From b684910604b4672a0bbdfbe5944f8a980c16b9e3 Mon Sep 17 00:00:00 2001 From: enitrat Date: Thu, 18 Sep 2025 16:06:06 +0100 Subject: [PATCH] dev: update cairo book source --- python/pyproject.toml | 3 - .../generated/openzeppelin_docs_summary.md | 19067 ---------------- .../optimizers/retrieval_optimizer.py | 5 +- python/src/scripts/summarizer/cli.py | 6 +- .../src/scripts/summarizer/dpsy_summarizer.py | 15 +- .../generated/cairo_book_summary.md | 16727 +++++--------- python/uv.lock | 499 +- 7 files changed, 6504 insertions(+), 29818 deletions(-) delete mode 100644 python/scripts/summarizer/generated/openzeppelin_docs_summary.md diff --git a/python/pyproject.toml b/python/pyproject.toml index 35748f41..3397567d 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -51,9 +51,6 @@ dependencies = [ "tenacity>=8.0.0", "toml>=0.10.2", "tqdm>=4.66.0", - "typer>=0.15.0", - "uvicorn[standard]>=0.36.0", - "websockets>=13.0", ] [project.optional-dependencies] diff --git a/python/scripts/summarizer/generated/openzeppelin_docs_summary.md b/python/scripts/summarizer/generated/openzeppelin_docs_summary.md deleted file mode 100644 index d6158a7b..00000000 --- a/python/scripts/summarizer/generated/openzeppelin_docs_summary.md +++ /dev/null @@ -1,19067 +0,0 @@ -# docs.openzeppelin.com — Snapshot (2025-08-02) - -Clean documentation content extracted from sitemap. - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/ - -## Contracts for Cairo - OpenZeppelin Docs - -# Contracts for Cairo - -**A library for secure smart contract development** written in Cairo for Starknet. This library consists of a set of reusable components to build custom smart contracts, as well as -ready-to-deploy presets. You can also find other utilities including interfaces and dispatchers and test utilities -that facilitate testing with Starknet Foundry. - -| | | -| --- | --- | -| | This repo contains highly experimental code. Expect rapid iteration. **Use at your own risk.** | - -| | | -| --- | --- | -| | You can track our roadmap and future milestones in our Github Project. | - -## Installation - -The library is available as a Scarb package. Follow this guide for installing Cairo and Scarb on your machine -before proceeding, and run the following command to check that the installation was successful: - -``` -$ scarb --version - -scarb 2.9.4 (d3be9ebe1 2025-02-19) -cairo: 2.9.4 (https://crates.io/crates/cairo-lang-compiler/2.9.4) -sierra: 1.6.0 -``` - -### Set up your project - -Create an empty directory, and `cd` into it: - -``` -mkdir my_project/ && cd my_project/ -``` - -Initialize a new Scarb project: - -``` -scarb init -``` - -The contents of `my_project/` should now look like this: - -``` -$ ls - -Scarb.toml src -``` - -### Install the library - -Install the library by declaring it as a dependency in the project’s `Scarb.toml` file: - -``` -[dependencies] -openzeppelin = "2.0.0" -``` - -The previous example would import the entire library. We can also add each package as a separate dependency to -improve the building time by not including modules that won’t be used: - -``` -[dependencies] -openzeppelin_access = "2.0.0" -openzeppelin_token = "2.0.0" -``` - -## Basic usage - -This is how it looks to build an ERC20 contract using the ERC20 component. -Copy the code into `src/lib.cairo`. - -``` -#[starknet::contract] -mod MyERC20Token { - // NOTE: If you added the entire library as a dependency, - // use `openzeppelin::token` instead. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -You can now compile it: - -``` -scarb build -``` - -Wizard → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/access - -## Access - OpenZeppelin Docs - -# Access - -Access control—​that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. -The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. -It is therefore critical to understand how you implement it, lest someone else -steals your whole system. - -## Ownership and `Ownable` - -The most common and basic form of access control is the concept of ownership: there’s an account that is the `owner` -of a contract and can do administrative tasks on it. -This approach is perfectly reasonable for contracts that have a single administrative user. - -OpenZeppelin Contracts for Cairo provides OwnableComponent for implementing ownership in your contracts. - -### Usage - -Integrating this component into a contract first requires assigning an owner. -The implementing contract’s constructor should set the initial owner by passing the owner’s address to Ownable’s -`initializer` like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Set the initial owner of the contract - self.ownable.initializer(owner); - } - - (...) -} -``` - -To restrict a function’s access to the owner only, add in the `assert_only_owner` method: - -``` -#[starknet::contract] -mod MyContract { - (...) - - #[external(v0)] - fn only_owner_allowed(ref self: ContractState) { - // This function can only be called by the owner - self.ownable.assert_only_owner(); - - (...) - } -} -``` - -### Interface - -This is the full interface of the `OwnableMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableABI { - // IOwnable - fn owner() -> ContractAddress; - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableCamelOnly - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -Ownable also lets you: - -* `transfer_ownership` from the owner account to a new one, and -* `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern - after an initial stage with centralized administration is over. - -| | | -| --- | --- | -| | Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` will no longer be callable! | - -### Two step transfer - -The component also offers a more robust way of transferring ownership via the -OwnableTwoStepImpl implementation. A two step transfer mechanism helps -to prevent unintended and irreversible owner transfers. Simply replace the `OwnableMixinImpl` -with its respective two step variant: - -``` -#[abi(embed_v0)] -impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl; -``` - -#### Interface - -This is the full interface of the two step `OwnableTwoStepMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableTwoStepABI { - // IOwnableTwoStep - fn owner() -> ContractAddress; - fn pending_owner() -> ContractAddress; - fn accept_ownership(); - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableTwoStepCamelOnly - fn pendingOwner() -> ContractAddress; - fn acceptOwnership(); - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -## Role-Based `AccessControl` - -While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of -authorization are often needed. You may want for an account to have permission to ban users from a system, but not -create new tokens. Role-Based Access Control (RBAC) offers -flexibility in this regard. - -In essence, we will be defining multiple roles, each allowed to perform different sets of actions. -An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for -instead of simply using `assert_only_owner`. This check can be enforced through `assert_only_role`. -Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. - -Most software uses access control systems that are role-based: some users are regular users, some may be supervisors -or managers, and a few will often have administrative privileges. - -### Usage - -For each role that you want to define, you will create a new *role identifier* that is used to grant, revoke, and -check if an account has that role. See Creating role identifiers for information -on creating identifiers. - -Here’s a simple example of implementing AccessControl on a portion of an ERC20 token contract which defines -and sets a 'minter' role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::MINTER_ROLE; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } -} -``` - -| | | -| --- | --- | -| | Make sure you fully understand how AccessControl works before using it on your system, or copy-pasting the examples from this guide. | - -While clear and explicit, this isn’t anything we wouldn’t have been able to achieve with -Ownable. Where AccessControl shines the most is in scenarios where granular -permissions are required, which can be implemented by defining *multiple* roles. - -Let’s augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress, - burner: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - self.accesscontrol._grant_role(BURNER_ROLE, burner); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -So clean! -By splitting concerns this way, more granular levels of permission may be implemented than were possible with the -simpler ownership approach to access control. Limiting what each component of a system is able to do is known -as the principle of least privilege, and is a good -security practice. Note that each account may still have more than one role, if so desired. - -### Granting and revoking roles - -The ERC20 token example above uses `_grant_role`, -an `internal` function that is useful when programmatically assigning -roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? - -By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making -the `assert_only_role` check pass. To grant and revoke roles dynamically, you will need help from the role’s *admin*. - -Every role has an associated admin role, which grants permission to call the -`grant_role` and -`revoke_role` functions. -A role can be granted or revoked by using these if the calling account has the corresponding admin role. -Multiple roles may have the same admin role to make management easier. -A role’s admin can even be the same role itself, which would cause accounts with that role to be able -to also grant and revoke it. - -This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also -provides an easy way to manage simpler applications. `AccessControl` includes a special role with the role identifier -of `0`, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. -An account with this role will be able to manage any other role, unless -`set_role_admin` is used to select a new admin role. - -Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - admin: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -| | | -| --- | --- | -| | The `grant_role` and `revoke_role` functions are automatically exposed as `external` functions from the `AccessControlImpl` by leveraging the `#[abi(embed_v0)]` annotation. | - -Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. -However, because those roles' admin role is the default admin role, and that role was granted to the 'admin', that -same account can call `grant_role` to give minting or burning permission, and `revoke_role` to remove it. - -Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary -over time. It can also be used to support use cases such as KYC, -where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -### Creating role identifiers - -In the Solidity implementation of AccessControl, contracts generally refer to the -keccak256 hash -of a role as the role identifier. - -For example: - -``` -bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") -``` - -These identifiers take up 32 bytes (256 bits). - -Cairo field elements (`felt252`) store a maximum of 252 bits. -With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. -Some ideas to consider: - -* Use sn\_keccak instead. -* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the - Cairo corelib. - -| | | -| --- | --- | -| | The `selector!` macro can be used to compute sn\_keccak in Cairo. | - -### Interface - -This is the full interface of the `AccessControlMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccessControlABI { - // IAccessControl - fn has_role(role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(role: felt252) -> felt252; - fn grant_role(role: felt252, account: ContractAddress); - fn revoke_role(role: felt252, account: ContractAddress); - fn renounce_role(role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(role: felt252) -> felt252; - fn grantRole(role: felt252, account: ContractAddress); - fn revokeRole(role: felt252, account: ContractAddress); - fn renounceRole(role: felt252, account: ContractAddress); - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -`AccessControl` also lets you `renounce_role` from the calling account. -The method expects an account as input as an extra security measure, to ensure you are -not renouncing a role from an unintended account. - -← SNIP12 and Typed Messages - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/accounts - -## Accounts - OpenZeppelin Docs - -# Accounts - -Unlike Ethereum where accounts are derived from a private key, all Starknet accounts are contracts. This means there’s no Externally Owned Account (EOA) -concept on Starknet. - -Instead, the network features native account abstraction and signature validation happens at the contract level. - -For a general overview of account abstraction, see -Starknet’s documentation. -A more detailed discussion on the topic can be found in -Starknet Shaman’s forum. - -| | | -| --- | --- | -| | For detailed information on the usage and implementation check the API Reference section. | - -## What is an account? - -Accounts in Starknet are smart contracts, and so they can be deployed and interacted -with like any other contract, and can be extended to implement any custom logic. However, an account is a special type -of contract that is used to validate and execute transactions. For this reason, it must implement a set of entrypoints -that the protocol uses for this execution flow. The SNIP-6 proposal defines a standard interface for accounts, -supporting this execution flow and interoperability with DApps in the ecosystem. - -### ISRC6 Interface - -``` -/// Represents a call to a target contract function. -struct Call { - to: ContractAddress, - selector: felt252, - calldata: Span -} - -/// Standard Account Interface -#[starknet::interface] -pub trait ISRC6 { - /// Executes a transaction through the account. - fn __execute__(calls: Array); - - /// Asserts whether the transaction is valid to be executed. - fn __validate__(calls: Array) -> felt252; - - /// Asserts whether a given signature for a given hash is valid. - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; -} -``` - -| | | -| --- | --- | -| | The `calldata` member of the `Call` struct in the accounts has been updated to `Span` for optimization purposes, but the interface ID remains the same for backwards compatibility. This inconsistency will be fixed in future releases. | - -SNIP-6 adds the `is_valid_signature` method. This method is not used by the protocol, but it’s useful for -DApps to verify the validity of signatures, supporting features like Sign In with Starknet. - -SNIP-6 also defines that compliant accounts must implement the SRC5 interface following SNIP-5, as -a mechanism for detecting whether a contract is an account or not through introspection. - -### ISRC5 Interface - -``` -/// Standard Interface Detection -#[starknet::interface] -pub trait ISRC5 { - /// Queries if a contract implements a given interface. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -SNIP-6 compliant accounts must return `true` when queried for the ISRC6 interface ID. - -Even though these interfaces are not enforced by the protocol, it’s recommended to implement them for enabling -interoperability with the ecosystem. - -### Protocol-level methods - -The Starknet protocol uses a few entrypoints for abstracting the accounts. We already mentioned the first two -as part of the ISRC6 interface, and both are required for enabling accounts to be used for executing transactions. The rest are optional: - -1. `__validate__` verifies the validity of the transaction to be executed. This is usually used to validate signatures, - but the entrypoint implementation can be customized to feature any validation mechanism with some limitations. -2. `__execute__` executes the transaction if the validation is successful. -3. `__validate_declare__` optional entrypoint similar to `__validate__` but for transactions - meant to declare other contracts. -4. `__validate_deploy__` optional entrypoint similar to `__validate__` but meant for counterfactual deployments. - -| | | -| --- | --- | -| | Although these entrypoints are available to the protocol for its regular transaction flow, they can also be called like any other method. | - -## Starknet Account - -Starknet native account abstraction pattern allows for the creation of custom accounts with different validation schemes, but -usually most account implementations validate transactions using the Stark curve which is the most efficient way -of validating signatures since it is a STARK-friendly curve. - -OpenZeppelin Contracts for Cairo provides AccountComponent for implementing this validation scheme. - -### Usage - -Constructing an account contract requires integrating both AccountComponent and SRC5Component. The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `AccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; - - // IPublicKey - fn get_public_key() -> felt252; - fn set_public_key(new_public_key: felt252, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IPublicKeyCamel - fn getPublicKey() -> felt252; - fn setPublicKey(newPublicKey: felt252, signature: Span); -} -``` - -## Ethereum Account - -Besides the Stark-curve account, OpenZeppelin Contracts for Cairo also offers Ethereum-flavored accounts that use the secp256k1 curve for signature validation. -For this the EthAccountComponent must be used. - -### Usage - -Constructing a secp256k1 account contract also requires integrating both EthAccountComponent and SRC5Component. -The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. -Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ClassHash; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `EthAccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait EthAccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IEthDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey - ) -> felt252; - - // IEthPublicKey - fn get_public_key() -> EthPublicKey; - fn set_public_key(new_public_key: EthPublicKey, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IEthPublicKeyCamel - fn getPublicKey() -> EthPublicKey; - fn setPublicKey(newPublicKey: EthPublicKey, signature: Span); -} -``` - -## Deploying an account - -In Starknet there are two ways of deploying smart contracts: using the `deploy_syscall` and doing -counterfactual deployments. -The former can be easily done with the Universal Deployer Contract (UDC), a contract that -wraps and exposes the `deploy_syscall` to provide arbitrary deployments through regular contract calls. -But if you don’t have an account to invoke it, you will probably want to use the latter. - -To do counterfactual deployments, you need to implement another protocol-level entrypoint named -`__validate_deploy__`. Check the counterfactual deployments guide to learn how. - -## Sending transactions - -Let’s now explore how to send transactions through these accounts. - -### Starknet Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -To deploy the account variant, compile the contract and declare the class hash because custom accounts are likely not declared. -This means that you’ll need an account already deployed. - -Next, create the account JSON with Starknet Foundry’s custom account setup and include the `--class-hash` flag with the declared class hash. -The flag enables custom account variants. - -| | | -| --- | --- | -| | The following examples use `sncast` v0.23.0. | - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account create \ - --name my-custom-account \ - --class-hash 0x123456... -``` - -This command will output the precomputed contract address and the recommended `max-fee`. -To counterfactually deploy the account, send funds to the address and then deploy the custom account. - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account deploy \ - --name my-custom-account -``` - -Once the account is deployed, set the `--account` flag with the custom account name to send transactions from that account. - -``` -$ sncast \ - --account my-custom-account \ - --url http://127.0.0.1:5050 \ - invoke \ - --contract-address 0x123... \ - --function "some_function" \ - --calldata 1 2 3 -``` - -### Ethereum Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -Special tooling is required in order to deploy and send transactions with an Ethereum-flavored account contract. -The following examples utilize the StarknetJS library. - -Compile and declare the contract on the target network. -Next, precompute the EthAccount contract address using the declared class hash. - -| | | -| --- | --- | -| | The following examples use unreleased features from StarknetJS (`starknetjs@next`) at commit d002baea0abc1de3ac6e87a671f3dec3757437b3. | - -``` -import * as dotenv from 'dotenv'; -import { CallData, EthSigner, hash } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Calculate EthAccount address -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountClassHash = ''; -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployerAddress = '0x0'; -const ethContractAddress = hash.calculateContractAddressFromHash( - salt, - ethAccountClassHash, - ethAccountConstructorCalldata, - deployerAddress -); -console.log('Pre-calculated EthAccount address: ', ethContractAddress); -``` - -Send funds to the pre-calculated EthAccount address and deploy the contract. - -``` -import * as dotenv from 'dotenv'; -import { Account, CallData, EthSigner, RpcProvider, stark } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare payload -const ethAccountClassHash = '' -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployPayload = { - classHash: ethAccountClassHash, - constructorCalldata: ethAccountConstructorCalldata, - addressSalt: salt, -}; - -// Deploy -const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); -const { transaction_hash, contract_address } = await ethAccount.deployAccount( - deployPayload, - { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } -); -await provider.waitForTransaction(transaction_hash); -console.log('EthAccount deployed at: ', contract_address); -``` - -Once deployed, connect the EthAccount instance to the target contract which enables calls to come from the EthAccount. -Here’s what an ERC20 transfer from an EthAccount looks like. - -``` -import * as dotenv from 'dotenv'; -import { Account, RpcProvider, Contract, EthSigner } from 'starknet'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare target contract -const erc20 = new Contract(compiledErc20.abi, erc20Address, provider); - -// Connect EthAccount with the target contract -erc20.connect(ethAccount); - -// Execute ERC20 transfer -const transferCall = erc20.populate('transfer', { - recipient: recipient.address, - amount: 50n -}); -const tx = await erc20.transfer( - transferCall.calldata, { maxFee: 900_000_000_000_000 } -); -await provider.waitForTransaction(tx.transaction_hash); -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/backwards-compatibility - -## Backwards Compatibility - OpenZeppelin Docs - -# Backwards Compatibility - -OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees. - -Bear in mind that while releasing versions, we treat minors as majors and patches as minors, in accordance with semantic versioning. This means that `v2.1.0` could be adding features to `v2.0.0`, while `v3.0.0` would be considered a breaking release. - -## API - -In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below. - -### Security - -Infrequently, a patch or minor update will remove or change an API in a breaking way but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory. - -### Errors - -The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified. - -### Major releases - -Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem. - -An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another. - -In the case of breaking "upgrade compatibility", an entry to the changelog will be added listing those breaking changes. - -## Storage layout - -Patch updates will always preserve storage layout compatibility, and after `v2.0.0` minors will too. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent. - -## Cairo version - -The minimum Cairo version required to compile the contracts will remain unchanged for patch updates, but it may change for minors. - -← Test Utilities - -Contracts for Solidity → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/components - -## Components - OpenZeppelin Docs - -# Components - -The following documentation provides reasoning and examples on how to use Contracts for Cairo components. - -Starknet components are separate modules that contain storage, events, and implementations that can be integrated into a contract. -Components themselves cannot be declared or deployed. -Another way to think of components is that they are abstract modules that must be instantiated. - -| | | -| --- | --- | -| | For more information on the construction and design of Starknet components, see the Starknet Shamans post and the Cairo book. | - -## Building a contract - -### Setup - -The contract should first import the component and declare it with the `component!` macro: - -``` -#[starknet::contract] -mod MyContract { - // Import the component - use openzeppelin_security::InitializableComponent; - - // Declare the component - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); -} -``` - -The `path` argument should be the imported component itself (in this case, InitializableComponent). -The `storage` and `event` arguments are the variable names that will be set in the `Storage` struct and `Event` enum, respectively. -Note that even if the component doesn’t define any events, the compiler will still create an empty event enum inside the component module. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -The `#[substorage(v0)]` attribute must be included for each component in the `Storage` trait. -This allows the contract to have indirect access to the component’s storage. -See Accessing component storage for more on this. - -The `#[flat]` attribute for events in the `Event` enum, however, is not required. -For component events, the first key in the event log is the component ID. -Flattening the component event removes it, leaving the event ID as the first key. - -### Implementations - -Components come with granular implementations of different interfaces. -This allows contracts to integrate only the implementations that they’ll use and avoid unnecessary bloat. -Integrating an implementation looks like this: - -``` -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - // Gives the contract access to the implementation methods - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -Defining an `impl` gives the contract access to the methods within the implementation from the component. -For example, `is_initialized` is defined in the `InitializableImpl`. -A function on the contract level can expose it like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - impl InitializableImpl = - InitializableComponent::InitializableImpl; - - #[external(v0)] - fn is_initialized(ref self: ContractState) -> bool { - self.initializable.is_initialized() - } -} -``` - -While there’s nothing wrong with manually exposing methods like in the previous example, this process can be tedious for implementations with many methods. -Fortunately, a contract can embed implementations which will expose all of the methods of the implementation. -To embed an implementation, add the `#[abi(embed_v0)]` attribute above the `impl`: - -``` -#[starknet::contract] -mod MyContract { - (...) - - // This attribute exposes the methods of the `impl` - #[abi(embed_v0)] - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -`InitializableImpl` defines the `is_initialized` method in the component. -By adding the embed attribute, `is_initialized` becomes a contract entrypoint for `MyContract`. - -| | | -| --- | --- | -| | Embeddable implementations, when available in this library’s components, are segregated from the internal component implementation which makes it easier to safely expose. Components also separate granular implementations from mixin implementations. The API documentation design reflects these groupings. See ERC20Component as an example which includes: * **Embeddable Mixin Implementation** * **Embeddable Implementations** * **Internal Implementations** * **Events** | - -### Mixins - -Mixins are impls made of a combination of smaller, more specific impls. -While separating components into granular implementations offers flexibility, -integrating components with many implementations can appear crowded especially if the contract uses all of them. -Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. - -Compare the following code blocks to see the benefit of using a mixin when creating an account contract. - -#### Account without mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl SRC6Impl = AccountComponent::SRC6Impl; -#[abi(embed_v0)] -impl DeclarerImpl = AccountComponent::DeclarerImpl; -#[abi(embed_v0)] -impl DeployableImpl = AccountComponent::DeployableImpl; -#[abi(embed_v0)] -impl PublicKeyImpl = AccountComponent::PublicKeyImpl; -#[abi(embed_v0)] -impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; -#[abi(embed_v0)] -impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; - -#[abi(embed_v0)] -impl SRC5Impl = SRC5Component::SRC5Impl; -``` - -#### Account with mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl AccountMixinImpl = AccountComponent::AccountMixinImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; -``` - -The rest of the setup for the contract, however, does not change. -This means that component dependencies must still be included in the `Storage` struct and `Event` enum. -Here’s a full example of an account contract that embeds the `AccountMixinImpl`: - -``` -#[starknet::contract] -mod Account { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // This embeds all of the methods from the many AccountComponent implementations - // and also includes `supports_interface` from `SRC5Impl` - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Initializers - -| | | -| --- | --- | -| | Failing to use a component’s `initializer` can result in irreparable contract deployments. Always read the API documentation for each integrated component. | - -Some components require some sort of setup upon construction. -Usually, this would be a job for a constructor; however, components themselves cannot implement constructors. -Components instead offer `initializer`s within their `InternalImpl` to call from the contract’s constructor. -Let’s look at how a contract would integrate OwnableComponent: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Invoke ownable's `initializer` - self.ownable.initializer(owner); - } -} -``` - -### Immutable Config - -While initializers help set up the component’s initial state, some require configuration that may be defined -as constants, saving gas by avoiding the necessity of reading from storage each time the variable needs to be used. The -Immutable Component Config pattern helps with this matter by allowing the implementing contract to define a set of -constants declared in the component, customizing its functionality. - -| | | -| --- | --- | -| | The Immutable Component Config standard is defined in the SRC-107. | - -Here’s an example of how to use the Immutable Component Config pattern with the ERC2981Component: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::common::erc2981::ERC2981Component; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc2981: ERC2981Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC2981Event: ERC2981Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - // Define the immutable config - pub impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig { - const FEE_DENOMINATOR: u128 = 10_000; - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### Default config - -Sometimes, components implementing the Immutable Component Config pattern provide a default configuration that can be -directly used without implementing the `ImmutableConfig` trait locally. When provided, this implementation will be named -`DefaultConfig` and will be available in the same module containing the component, as a sibling. - -In the following example, the `DefaultConfig` trait is used to define the `FEE_DENOMINATOR` config constant. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - // Bring the DefaultConfig trait into scope - use openzeppelin_token::common::erc2981::{ERC2981Component, DefaultConfig}; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - (...) - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - (...) - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### `validate` function - -The `ImmutableConfig` trait may also include a `validate` function with a default implementation, which -asserts that the configuration is correct, and must not be overridden by the implementing contract. For more information -on how to use this function, refer to the validate section of the SRC-107. - -### Dependencies - -Some components include dependencies of other components. -Contracts that integrate components with dependencies must also include the component dependency. -For instance, AccessControlComponent depends on SRC5Component. -Creating a contract with `AccessControlComponent` should look like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - #[abi(embed_v0)] - impl AccessControlCamelImpl = - AccessControlComponent::AccessControlCamelImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - (...) -} -``` - -## Customization - -| | | -| --- | --- | -| | Customizing implementations and accessing component storage can potentially corrupt the state, bypass security checks, and undermine the component logic. **Exercise extreme caution**. See Security. | - -### Hooks - -Hooks are entrypoints to the business logic of a token component that are accessible at the contract level. -This allows contracts to insert additional behaviors before and/or after token transfers (including mints and burns). -Prior to hooks, extending functionality required contracts to create custom implementations. - -All token components include a generic hooks trait that include empty default functions. -When creating a token contract, the using contract must create an implementation of the hooks trait. -Suppose an ERC20 contract wanted to include Pausable functionality on token transfers. -The following snippet leverages the `before_update` hook to include this behavior. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_security::pausable::PausableComponent::InternalTrait; - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // Create the hooks implementation - impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { - // Occurs before token transfers - fn before_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - // Access local state from component state - let contract_state = self.get_contract(); - // Call function from integrated component - contract_state.pausable.assert_not_paused(); - } - - // Omitting the `after_update` hook because the default behavior - // is already implemented in the trait - } - - (...) -} -``` - -Notice that the `self` parameter expects a component state type. -Instead of passing the component state, the using contract’s state can be passed which simplifies the syntax. -The hook then moves the scope up with the Cairo-generated `get_contract` through the `HasComponent` trait (as illustrated with ERC20Component in this example). -From here, the hook can access the using contract’s integrated components, storage, and implementations. - -Be advised that even if a token contract does not require hooks, the hooks trait must still be implemented. -The using contract may instantiate an empty impl of the trait; -however, the Contracts for Cairo library already provides the instantiated impl to abstract this away from contracts. -The using contract just needs to bring the implementation into scope like this: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_token::erc20::ERC20HooksEmptyImpl; - - (...) -} -``` - -| | | -| --- | --- | -| | For a more in-depth guide on hooks, see Extending Cairo Contracts with Hooks. | - -### Custom implementations - -There are instances where a contract requires different or amended behaviors from a component implementation. -In these scenarios, a contract must create a custom implementation of the interface. -Let’s break down a pausable ERC20 contract to see what that looks like. -Here’s the setup: - -``` -#[starknet::contract] -mod ERC20Pausable { - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - // Import the ERC20 interfaces to create custom implementations - use openzeppelin_token::erc20::interface::{IERC20, IERC20CamelOnly}; - use starknet::ContractAddress; - - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // `ERC20MetadataImpl` can keep the embed directive because the implementation - // will not change - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - // Do not add the embed directive to these implementations because - // these will be customized - impl ERC20Impl = ERC20Component::ERC20Impl; - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -The first thing to notice is that the contract imports the interfaces of the implementations that will be customized. -These will be used in the next code example. - -Next, the contract includes the ERC20Component implementations; however, `ERC20Impl` and `ERC20CamelOnlyImplt` are **not** embedded. -Instead, we want to expose our custom implementation of an interface. -The following example shows the pausable logic integrated into the ERC20 implementations: - -``` -#[starknet::contract] -mod ERC20Pausable { - (...) - - // Custom ERC20 implementation - #[abi(embed_v0)] - impl CustomERC20Impl of IERC20 { - fn transfer( - ref self: ContractState, recipient: ContractAddress, amount: u256 - ) -> bool { - // Add the custom logic - self.pausable.assert_not_paused(); - // Add the original implementation method from `IERC20Impl` - self.erc20.transfer(recipient, amount) - } - - fn total_supply(self: @ContractState) -> u256 { - // This method's behavior does not change from the component - // implementation, but this method must still be defined. - // Simply add the original implementation method from `IERC20Impl` - self.erc20.total_supply() - } - - (...) - } - - // Custom ERC20CamelOnly implementation - #[abi(embed_v0)] - impl CustomERC20CamelOnlyImpl of IERC20CamelOnly { - fn totalSupply(self: @ContractState) -> u256 { - self.erc20.total_supply() - } - - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - self.erc20.balance_of(account) - } - - fn transferFrom( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - self.pausable.assert_not_paused(); - self.erc20.transfer_from(sender, recipient, amount) - } - } -} -``` - -Notice that in the `CustomERC20Impl`, the `transfer` method integrates `pausable.assert_not_paused` as well as `erc20.transfer` from `PausableImpl` and `ERC20Impl` respectively. -This is why the contract defined the `ERC20Impl` from the component in the previous example. - -Creating a custom implementation of an interface must define **all** methods from that interface. -This is true even if the behavior of a method does not change from the component implementation (as `total_supply` exemplifies in this example). - -### Accessing component storage - -There may be cases where the contract must read or write to an integrated component’s storage. -To do so, use the same syntax as calling an implementation method except replace the name of the method with the storage variable like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - (...) - - fn write_to_comp_storage(ref self: ContractState) { - self.initializable.Initializable_initialized.write(true); - } - - fn read_from_comp_storage(self: @ContractState) -> bool { - self.initializable.Initializable_initialized.read() - } -} -``` - -## Security - -The maintainers of OpenZeppelin Contracts for Cairo are mainly concerned with the correctness and security of the code as published in the library. - -Customizing implementations and manipulating the component state may break some important assumptions and introduce vulnerabilities. -While we try to ensure the components remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. -Any and all customizations to the component logic should be carefully reviewed and checked against the source code of the component they are customizing so as to fully understand their impact and guarantee their security. - -← Wizard - -Presets → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/erc1155 - -## ERC1155 - OpenZeppelin Docs - -# ERC1155 - -The ERC1155 multi token standard is a specification for fungibility-agnostic token contracts. -The ERC1155 library implements an approximation of EIP-1155 in Cairo for StarkNet. - -## Multi Token Standard - -The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This -is why its balance\_of function differs from ERC20’s and ERC777’s: it has an additional ID argument for the -identifier of the token that you want to query the balance of. - -This is similar to how ERC721 does things, but in that standard a token ID has no concept of balance: each token is -non-fungible and exists or doesn’t. The ERC721 balance\_of function refers to how many different tokens an account -has, not how many of each. On the other hand, in ERC1155 accounts have a distinct balance for each token ID, and -non-fungible tokens are implemented by simply minting a single one of them. - -This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new -contract for each token type, a single ERC1155 token contract can hold the entire system state, reducing deployment -costs and complexity. - -## Usage - -Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s URI and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyERC1155 { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155 Mixin - #[abi(embed_v0)] - impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; - impl ERC1155InternalImpl = ERC1155Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155: ERC1155Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155Event: ERC1155Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - token_uri: ByteArray, - recipient: ContractAddress, - token_ids: Span, - values: Span - ) { - self.erc1155.initializer(token_uri); - self - .erc1155 - .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC1155Component. -The interface includes the IERC1155 standard interface and the optional IERC1155MetadataURI interface together with ISRC5. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC1155ABI { - // IERC1155 - fn balance_of(account: ContractAddress, token_id: u256) -> u256; - fn balance_of_batch( - accounts: Span, token_ids: Span - ) -> Span; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - value: u256, - data: Span - ); - fn safe_batch_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ); - fn is_approved_for_all( - owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn set_approval_for_all(operator: ContractAddress, approved: bool); - - // IERC1155MetadataURI - fn uri(token_id: u256) -> ByteArray; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IERC1155Camel - fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; - fn balanceOfBatch( - accounts: Span, tokenIds: Span - ) -> Span; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - value: u256, - data: Span - ); - fn safeBatchTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenIds: Span, - values: Span, - data: Span - ); - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - fn setApprovalForAll(operator: ContractAddress, approved: bool); -} -``` - -## ERC1155 Compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: - -* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. -* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. -* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. - -## Batch operations - -Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, balance\_of\_batch and safe\_batch\_transfer\_from, that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have safe\_transfer\_from for non-batch operations. - -In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as -batch\_mint\_with\_acceptance\_check. - -| | | -| --- | --- | -| | While safe\_transfer\_from and safe\_batch\_transfer\_from prevent loss by checking the receiver can handle the tokens, this yields execution to the receiver which can result in a reentrant call. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC1155 tokens, said contract must implement the `IERC1155Receiver` interface. -The recipient contract must also implement the SRC5 interface which supports interface introspection. - -### IERC1155Receiver - -``` -#[starknet::interface] -pub trait IERC1155Receiver { - fn on_erc1155_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - value: u256, - data: Span - ) -> felt252; - fn on_erc1155_batch_received( - operator: ContractAddress, - from: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC1155Receiver` interface exposes the on\_erc1155\_received and on\_erc1155\_batch\_received methods. -When safe\_transfer\_from and safe\_batch\_transfer\_from are called, they invoke the recipient contract’s `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which **must** return the IERC1155Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo ERC1155ReceiverComponent already returns the correct interface ID for safe token transfers. -To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::ERC1155ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155Receiver Mixin - #[abi(embed_v0)] - impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl; - impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155_receiver: ERC1155ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc1155_receiver.initializer(); - } -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/erc20 - -## ERC20 - OpenZeppelin Docs - -# ERC20 - -The ERC20 token standard is a specification for fungible tokens, a type of token where all the units are exactly equal to each other. -`token::erc20::ERC20Component` provides an approximation of EIP-20 in Cairo for Starknet. - -| | | -| --- | --- | -| | Prior to Contracts v0.7.0, ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`. If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract **must** use a custom `decimals` implementation. See the Customizing decimals guide. | - -## Usage - -Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and instantiating the token implementation. -Here’s what that looks like: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } -} -``` - -`MyToken` integrates both the `ERC20Impl` and `ERC20MetadataImpl` with the embed directive which marks the implementations as external in the contract. -While the `ERC20MetadataImpl` is optional, it’s generally recommended to include it because the vast majority of ERC20 tokens provide the metadata methods. -The above example also includes the `ERC20InternalImpl` instance. -This allows the contract’s constructor to initialize the contract and create an initial supply of tokens. - -| | | -| --- | --- | -| | For a more complete guide on ERC20 token mechanisms, see Creating ERC20 Supply. | - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC20Component. -The interface includes the IERC20 standard interface as well as the optional IERC20Metadata. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes an implementation of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20Camel - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -## ERC20 compatibility - -Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. -Some notable differences, however, can still be found, such as: - -* The `ByteArray` type is used to represent strings in Cairo. -* The component offers a dual interface which supports both snake\_case and camelCase methods, as opposed to just camelCase in Solidity. -* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. -* Function selectors are calculated differently between Cairo and Solidity. - -## Customizing decimals - -Cairo, like Solidity, does not support floating-point numbers. -To get around this limitation, ERC20 token contracts may offer a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed. -For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`. -An outside interface would display the token supply as `1.234`. -In the actual contract, however, the supply would still be the integer `1234`. -In other words, **the decimals field in no way changes the actual arithmetic** because all operations are still performed on integers. - -Most contracts use `18` decimals and this was even proposed to be compulsory (see the EIP discussion). - -### The static approach (SRC-107) - -The Contracts for Cairo `ERC20` component leverages SRC-107 to allow for a static and configurable number of decimals. -To use the default `18` decimals, you can use the `DefaultConfig` implementation by just importing it: - -``` -#[starknet::contract] -mod MyToken { - // Importing the DefaultConfig implementation would make decimals 18 by default. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -To customize this value, you can implement the ImmutableConfig trait locally in the contract. -The following example shows how to set the decimals to `6`: - -``` -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - // Custom implementation of the ERC20Component ImmutableConfig. - impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig { - const DECIMALS: u8 = 6; - } -} -``` - -### The storage approach - -For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate. - -| | | -| --- | --- | -| | Note that we are not using the MixinImpl or the DefaultConfig in this case, since we need to customize the IERC20Metadata implementation. | - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::interface; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - // The decimals value is stored locally - decimals: u8, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress, - ) { - // Call the internal function that writes decimals to storage - self._set_decimals(decimals); - - // Initialize ERC20 - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } - - #[abi(embed_v0)] - impl ERC20CustomMetadataImpl of interface::IERC20Metadata { - fn name(self: @ContractState) -> ByteArray { - self.erc20.ERC20_name.read() - } - - fn symbol(self: @ContractState) -> ByteArray { - self.erc20.ERC20_symbol.read() - } - - fn decimals(self: @ContractState) -> u8 { - self.decimals.read() - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn _set_decimals(ref self: ContractState, decimals: u8) { - self.decimals.write(decimals); - } - } -} -``` - -This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage. -Note that the `decimals` state variable must be defined in the contract’s storage because this variable does not exist in the component offered by OpenZeppelin Contracts for Cairo. -It’s important to include a custom ERC20 metadata implementation and NOT use the Contracts for Cairo `ERC20MetadataImpl` in this specific case since the `decimals` method will always return `18`. - -← API Reference - -Creating Supply → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/erc4626 - -## ERC4626 - OpenZeppelin Docs - -# ERC4626 - -ERC4626 is an extension of ERC20 that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault. - -We provide a base component of ERC4626 which is designed to allow developers to easily re-configure the vault’s behavior, using traits and hooks, while staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. - -## Security concern: Inflation attack - -### Visualizing the vault - -In exchange for the assets deposited into an ERC4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault. - -* If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets. -* If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets. - -In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line. - -When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets. - -In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. - -### The attack - -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit less than 1 share worth of tokens, you will receive 0 shares, effectively making a donation. - -For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. - -In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%. - -Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens. - -We can clearly see that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. - -The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe. - -Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited. - -An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. - -In math that gives: - -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(0\) | \(0\) | - | -| after attacker’s deposit | \(a\_0\) | \(a\_0\) | \(1\) | -| after attacker’s donation | \(a\_0+a\_1\) | \(a\_0\) | \(\frac{a\_0}{a\_0+a\_1}\) | - -This means a deposit of \(u\) will give \(\frac{u \times a\_0}{a\_0 + a\_1}\) shares. - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[\frac{u \times a\_0}{a\_0+a\_1} < 1 \iff u < 1 + \frac{a\_1}{a\_0}\] - -Using \(a\_0 = 1\) and \(a\_1 = u\) is enough. So the attacker only needs \(u+1\) assets to perform a successful attack. - -It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user’s deposit. In order to target \(\frac{u}{n}\), the user needs to suffer rounding of a similar fraction, which means the user must receive at most \(n\) shares. This results in: - -\[\frac{u \times a\_0}{a\_0+a\_1} < n \iff \frac{u}{n} < 1 + \frac{a\_1}{a\_0}\] - -In this scenario, the attack is \(n\) times less powerful (in how much it is stealing) and costs \(n\) times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings. - -### Defending with a virtual offset - -The defense we propose is based on the approach used in YieldBox. It consists of two parts: - -* Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets. -* Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty. - -These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable to perform an attack. - -Following the previous math definitions, we have: - -* \(\delta\) the vault offset -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(1\) | \(10^\delta\) | \(10^\delta\) | -| after attacker’s deposit | \(1+a\_0\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta\) | -| after attacker’s donation | \(1+a\_0+a\_1\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta \times \frac{1+a\_0}{1+a\_0+a\_1}\) | - -One important thing to note is that the attacker only owns a fraction \(\frac{a\_0}{1 + a\_0}\) of the shares, so when doing the donation, he will only be able to recover that fraction \(\frac{a\_1 \times a\_0}{1 + a\_0}\) of the donation. The remaining \(\frac{a\_1}{1+a\_0}\) are captured by the vault. - -\[\mathit{loss} = \frac{a\_1}{1+a\_0}\] - -When the user deposits \(u\), he receives - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1}\] - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1} < 1\] - -\[\iff 10^\delta \times u < \frac{1+a\_0+a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u < 1 + \frac{a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u \le \mathit{loss}\] - -* If the offset is 0, the attacker loss is at least equal to the user’s deposit. -* If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user. - -This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful. - -The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively. - -\(\delta = 3\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -\(\delta = 3\), \(a\_0 = 100\), \(a\_1 = 10^5\) - -\(\delta = 6\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -## Usage - -### Custom behavior: Adding fees to the vault - -In ERC4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. -In both cases, it is essential to remain compliant with the ERC4626 requirements in regard to the preview functions. - -For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `preview_deposit(100)`. -Similarly, `preview_mint` should account for the fees that the user will have to pay on top of share’s cost. - -As for the `Deposit` event, while this is less clear in the EIP spec itself, -there seems to be consensus that it should include the number of assets paid for by the user, including the fees. - -On the other hand, when withdrawing assets, the number given by the user should correspond to what the user receives. -Any fees should be added to the quote (in shares) performed by `preview_withdraw`. - -The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted). - -The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. -The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault. - -The following example describes how fees proportional to the deposited/withdrawn amount can be implemented: - -``` -/// The mock contract charges fees in terms of assets, not shares. -/// This means that the fees are calculated based on the amount of assets that are being deposited -/// or withdrawn, and not based on the amount of shares that are being minted or redeemed. -/// This is an opinionated design decision for the purpose of testing. -/// DO NOT USE IN PRODUCTION -#[starknet::contract] -pub mod ERC4626Fees { - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::FeeConfigTrait; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::InternalTrait as ERC4626InternalTrait; - use openzeppelin_token::erc20::extensions::erc4626::{DefaultConfig, ERC4626DefaultLimits}; - use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::math; - use openzeppelin_utils::math::Rounding; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: ERC4626Component, storage: erc4626, event: ERC4626Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC4626 - #[abi(embed_v0)] - impl ERC4626ComponentImpl = ERC4626Component::ERC4626Impl; - // ERC4626MetadataImpl is a custom impl of IERC20Metadata - #[abi(embed_v0)] - impl ERC4626MetadataImpl = ERC4626Component::ERC4626MetadataImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC4626InternalImpl = ERC4626Component::InternalImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc4626: ERC4626Component::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - pub entry_fee_basis_point_value: u256, - pub entry_fee_recipient: ContractAddress, - pub exit_fee_basis_point_value: u256, - pub exit_fee_recipient: ContractAddress, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC4626Event: ERC4626Component::Event, - #[flat] - ERC20Event: ERC20Component::Event, - } - - const _BASIS_POINT_SCALE: u256 = 10_000; - - /// - /// Hooks - /// - - impl ERC4626HooksImpl of ERC4626Component::ERC4626HooksTrait { - fn after_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let entry_basis_points = contract_state.entry_fee_basis_point_value.read(); - let fee = contract_state.fee_on_total(assets, entry_basis_points); - let recipient = contract_state.entry_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - - fn before_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let exit_basis_points = contract_state.exit_fee_basis_point_value.read(); - let fee = contract_state.fee_on_raw(assets, exit_basis_points); - let recipient = contract_state.exit_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - } - - /// Adjust fees - impl AdjustFeesImpl of FeeConfigTrait { - fn adjust_deposit( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_deposit(assets) - } - - fn adjust_mint( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_mint(assets) - } - - fn adjust_withdraw( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_withdraw(assets) - } - - fn adjust_redeem( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_redeem(assets) - } - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - underlying_asset: ContractAddress, - initial_supply: u256, - recipient: ContractAddress, - entry_fee: u256, - entry_treasury: ContractAddress, - exit_fee: u256, - exit_treasury: ContractAddress, - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - self.erc4626.initializer(underlying_asset); - - self.entry_fee_basis_point_value.write(entry_fee); - self.entry_fee_recipient.write(entry_treasury); - self.exit_fee_basis_point_value.write(exit_fee); - self.exit_fee_recipient.write(exit_treasury); - } - - #[generate_trait] - pub impl InternalImpl of InternalTrait { - fn transfer_fees(ref self: ContractState, recipient: ContractAddress, fee: u256) { - let asset_address = self.asset(); - let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; - assert(asset_dispatcher.transfer(recipient, fee), 'Fee transfer failed'); - } - - fn remove_fee_from_deposit(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_total(assets, self.entry_fee_basis_point_value.read()); - assets - fee - } - - fn add_fee_to_mint(self: @ContractState, assets: u256) -> u256 { - assets + self.fee_on_raw(assets, self.entry_fee_basis_point_value.read()) - } - - fn add_fee_to_withdraw(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_raw(assets, self.exit_fee_basis_point_value.read()); - assets + fee - } - - fn remove_fee_from_redeem(self: @ContractState, assets: u256) -> u256 { - assets - self.fee_on_total(assets, self.exit_fee_basis_point_value.read()) - } - - /// - /// Fee operations - /// - - /// Calculates the fees that should be added to an amount `assets` that does not already - /// include fees. - /// Used in IERC4626::mint and IERC4626::withdraw operations. - fn fee_on_raw(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, Rounding::Ceil) - } - - /// Calculates the fee part of an amount `assets` that already includes fees. - /// Used in IERC4626::deposit and IERC4626::redeem operations. - fn fee_on_total(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div( - assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, Rounding::Ceil, - ) - } - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC4626Component. -The full interface includes the IERC4626, IERC20, and IERC20Metadata interfaces. -Note that implementing the IERC20Metadata interface is a requirement of IERC4626. - -``` -#[starknet::interface] -pub trait ERC4626ABI { - // IERC4626 - fn asset() -> ContractAddress; - fn total_assets() -> u256; - fn convert_to_shares(assets: u256) -> u256; - fn convert_to_assets(shares: u256) -> u256; - fn max_deposit(receiver: ContractAddress) -> u256; - fn preview_deposit(assets: u256) -> u256; - fn deposit(assets: u256, receiver: ContractAddress) -> u256; - fn max_mint(receiver: ContractAddress) -> u256; - fn preview_mint(shares: u256) -> u256; - fn mint(shares: u256, receiver: ContractAddress) -> u256; - fn max_withdraw(owner: ContractAddress) -> u256; - fn preview_withdraw(assets: u256) -> u256; - fn withdraw( - assets: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - fn max_redeem(owner: ContractAddress) -> u256; - fn preview_redeem(shares: u256) -> u256; - fn redeem( - shares: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20CamelOnly - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/erc721 - -## ERC721 - OpenZeppelin Docs - -# ERC721 - -The ERC721 token standard is a specification for non-fungible tokens, or more colloquially: NFTs. -`token::erc721::ERC721Component` provides an approximation of EIP-721 in Cairo for Starknet. - -## Usage - -Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s name, symbol, and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyNFT { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721 Mixin - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721: ERC721Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - recipient: ContractAddress - ) { - let name = "MyNFT"; - let symbol = "NFT"; - let base_uri = "https://api.example.com/v1/"; - let token_id = 1; - - self.erc721.initializer(name, symbol, base_uri); - self.erc721.mint(recipient, token_id); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC721Component. -The interface includes the IERC721 standard interface and the optional IERC721Metadata interface. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC721ABI { - // IERC721 - fn balance_of(account: ContractAddress) -> u256; - fn owner_of(token_id: u256) -> ContractAddress; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); - fn approve(to: ContractAddress, token_id: u256); - fn set_approval_for_all(operator: ContractAddress, approved: bool); - fn get_approved(token_id: u256) -> ContractAddress; - fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn token_uri(token_id: u256) -> ByteArray; - - // IERC721CamelOnly - fn balanceOf(account: ContractAddress) -> u256; - fn ownerOf(tokenId: u256) -> ContractAddress; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ); - fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); - fn setApprovalForAll(operator: ContractAddress, approved: bool); - fn getApproved(tokenId: u256) -> ContractAddress; - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721MetadataCamelOnly - fn tokenURI(tokenId: u256) -> ByteArray; -} -``` - -## ERC721 compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. -This implementation does, however, include a few notable differences such as: - -* `interface_id`s are hardcoded and initialized by the constructor. - The hardcoded values derive from Starknet’s selector calculations. - See the Introspection docs. -* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. - The difference between both functions consists of accepting `data` as an argument. - `safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. - If `data` is not used, simply pass an empty array. -* ERC721 utilizes SRC5 to declare and query interface support on Starknet as opposed to Ethereum’s EIP165. - The design for `SRC5` is similar to OpenZeppelin’s ERC165Storage. -* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). - -## Token transfers - -This library includes transfer\_from and safe\_transfer\_from to transfer NFTs. -If using `transfer_from`, **the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.** -The `safe_transfer_from` method mitigates this risk by querying the recipient contract’s interface support. - -| | | -| --- | --- | -| | Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. -The recipient contract must also implement the SRC5 interface which, as described earlier, supports interface introspection. - -### IERC721Receiver - -``` -#[starknet::interface] -pub trait IERC721Receiver { - fn on_erc721_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC721Receiver` interface exposes the on\_erc721\_received method. -When safe methods such as safe\_transfer\_from and safe\_mint are called, they invoke the recipient contract’s `on_erc721_received` method which **must** return the IERC721Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. -To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721Receiver Mixin - #[abi(embed_v0)] - impl ERC721ReceiverMixinImpl = ERC721ReceiverComponent::ERC721ReceiverMixinImpl; - impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721_receiver: ERC721ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721ReceiverEvent: ERC721ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721_receiver.initializer(); - } -} -``` - -## Storing ERC721 URIs - -Token URIs were previously stored as single field elements prior to Cairo v0.2.5. -ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the token\_uri method. -This design mirrors OpenZeppelin’s default Solidity implementation for ERC721. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/finance - -## Finance - OpenZeppelin Docs - -# Finance - -This module includes primitives for financial systems. - -## Vesting component - -The VestingComponent manages the gradual release of ERC-20 tokens to a designated beneficiary based on a predefined vesting schedule. -The implementing contract must implement the OwnableComponent, where the contract owner is regarded as the vesting beneficiary. -This structure allows ownership rights of both the contract and the vested tokens to be assigned and transferred. - -| | | -| --- | --- | -| | Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning of the vesting period. As a result, if the vesting has already started, a portion of the newly transferred tokens may become immediately releasable. | - -| | | -| --- | --- | -| | By setting the duration to 0, it’s possible to configure this contract to behave like an asset timelock that holds tokens for a beneficiary until a specified date. | - -### Vesting schedule - -The VestingSchedule trait defines the logic for calculating the vested amount based on a given timestamp. This -logic is not part of the VestingComponent, so any contract implementing the VestingComponent must provide its own -implementation of the VestingSchedule trait. - -| | | -| --- | --- | -| | There’s a ready-made implementation of the VestingSchedule trait available named LinearVestingSchedule. It incorporates a cliff period by returning 0 vested amount until the cliff ends. After the cliff, the vested amount is calculated as directly proportional to the time elapsed since the beginning of the vesting schedule. | - -### Usage - -The contract must integrate VestingComponent and OwnableComponent as dependencies. The contract’s constructor -should initialize both components. Core vesting parameters, such as `beneficiary`, `start`, `duration` -and `cliff_duration`, are passed as arguments to the constructor and set at the time of deployment. - -The implementing contract must provide an implementation of the VestingSchedule trait. This can be achieved either by importing -a ready-made LinearVestingSchedule implementation or by defining a custom one. - -Here’s an example of a simple vesting wallet contract with a LinearVestingSchedule, where the vested amount -is calculated as being directly proportional to the time elapsed since the start of the vesting period. - -``` -#[starknet::contract] -mod LinearVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule}; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff_duration: u64 - ) { - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff_duration); - } -} -``` - -A vesting schedule will often follow a custom formula. In such cases, the VestingSchedule trait is useful. -To support a custom vesting schedule, the contract must provide an implementation of the -calculate\_vested\_amount function based on the desired formula. - -| | | -| --- | --- | -| | When using a custom VestingSchedule implementation, the LinearVestingSchedule must be excluded from the imports. | - -| | | -| --- | --- | -| | If there are additional parameters required for calculations, which are stored in the contract’s storage, you can access them using `self.get_contract()`. | - -Here’s an example of a vesting wallet contract with a custom VestingSchedule implementation, where tokens -are vested in a number of steps. - -``` -#[starknet::contract] -mod StepsVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait; - use openzeppelin_finance::vesting::VestingComponent; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - total_steps: u64, - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - total_steps: u64, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff: u64, - ) { - self.total_steps.write(total_steps); - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff); - } - - impl VestingSchedule of VestingScheduleTrait { - fn calculate_vested_amount( - self: @VestingComponent::ComponentState, - token: ContractAddress, - total_allocation: u256, - timestamp: u64, - start: u64, - duration: u64, - cliff: u64, - ) -> u256 { - if timestamp < cliff { - 0 - } else if timestamp >= start + duration { - total_allocation - } else { - let total_steps = self.get_contract().total_steps.read(); - let vested_per_step = total_allocation / total_steps.into(); - let step_duration = duration / total_steps; - let current_step = (timestamp - start) / step_duration; - let vested_amount = vested_per_step * current_step.into(); - vested_amount - } - } - } -} -``` - -### Interface - -Here is the full interface of a standard contract implementing the vesting functionality: - -``` -#[starknet::interface] -pub trait VestingABI { - // IVesting - fn start(self: @TState) -> u64; - fn cliff(self: @TState) -> u64; - fn duration(self: @TState) -> u64; - fn end(self: @TState) -> u64; - fn released(self: @TState, token: ContractAddress) -> u256; - fn releasable(self: @TState, token: ContractAddress) -> u256; - fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256; - fn release(ref self: TState, token: ContractAddress) -> u256; - - // IOwnable - fn owner(self: @TState) -> ContractAddress; - fn transfer_ownership(ref self: TState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TState); - - // IOwnableCamelOnly - fn transferOwnership(ref self: TState, newOwner: ContractAddress); - fn renounceOwnership(ref self: TState); -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/governance/governor - -## Governor - OpenZeppelin Docs - -# Governor - -Decentralized protocols are in constant evolution from the moment they are publicly released. Often, -the initial team retains control of this evolution in the first stages, but eventually delegates it -to a community of stakeholders. The process by which this community makes decisions is called -on-chain governance, and it has become a central component of decentralized protocols, fueling -varied decisions such as parameter tweaking, smart contract upgrades, integrations with other -protocols, treasury management, grants, etc. - -This governance protocol is generally implemented in a special-purpose contract called “Governor”. In -OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different -requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, -but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. - -## Usage and setup - -### Token - -The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has -to implement the VotesComponent extension. This extension will keep track of historical balances so that voting power -is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. - -If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a -governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. - -| | | -| --- | --- | -| | The library currently does not include a wrapper for tokens, but it will be added in a future release. | - -| | | -| --- | --- | -| | Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, block numbers instead. | - -### Governor - -We will initially build a Governor without a timelock. The core logic is given by the GovernorComponent, but we -still need to choose: - -1) how voting power is determined, - -2) how many votes are needed for quorum, - -3) what options people have when casting a vote and how those votes are counted, and - -4) the execution mechanism that should be used. - -Each of these aspects is customizable by writing your own extensions, -or more easily choosing one from the library. - -**For 1)** we will use the GovernorVotes extension, which hooks to an IVotes instance to determine the voting power -of an account based on the token balance they hold when a proposal becomes active. -This module requires the address of the token to be passed as an argument to the initializer. - -**For 2)** we will use GovernorVotesQuorumFraction. This works together with the IVotes instance to define the quorum as a -percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer -parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator -is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 4%). - -**For 3)** we will use GovernorCountingSimple, an extension that offers 3 options to voters: For, Against, and Abstain, -and where only For and Abstain votes are counted towards quorum. - -**For 4)** we will use GovernorCoreExecution, an extension that allows proposal execution directly through the governor. - -| | | -| --- | --- | -| | Another option is GovernorTimelockExecution. An example can be found in the next section. | - -Besides these, we also need an implementation for the GovernorSettingsTrait defining the voting delay, voting period, -and proposal threshold. While we can use the GovernorSettings extension which allows to set these parameters by the -governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, -and proposal threshold as constant values. - -*voting\_delay*: How long after a proposal is created should voting power be fixed. A large voting delay gives -users time to unstake tokens if necessary. - -*voting\_period*: How long does a proposal remain open to votes. - -| | | -| --- | --- | -| | These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. | - -*proposal\_threshold*: This restricts proposal creation to accounts who have enough voting power. - -An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used -the `DefaultConfig`. Check the Immutable Component Config guide for more details. - -The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. - -``` -#[starknet::contract] -mod MyGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, - GovernorCoreExecutionComponent, - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM_NUMERATOR: u256 = 40; // 4% - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!( - path: GovernorVotesQuorumFractionComponent, - storage: governor_votes, - event: GovernorVotesEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorCoreExecutionComponent, - storage: governor_core_execution, - event: GovernorCoreExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl QuorumFractionImpl = - GovernorVotesQuorumFractionComponent::QuorumFractionImpl; - - // Extensions internal - impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; - impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorCoreExecutionImpl = - GovernorCoreExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_core_execution: GovernorCoreExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, votes_token: ContractAddress) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { - /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. - fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_DELAY - } - - /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. - fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_PERIOD - } - - /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. - fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { - PROPOSAL_THRESHOLD - } - } -} -``` - -### Timelock - -It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree -with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the -GovernorTimelockExecution extension. - -| | | -| --- | --- | -| | When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. | - -TimelockController uses an AccessControl setup that we need to understand in order to set up roles. - -The Proposer role is in charge of queueing operations: this is the role the Governor instance must be granted, -and it MUST be the only proposer (and canceller) in the system. - -The Executor role is in charge of executing already available operations: we can assign this role to the special -zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). - -The Canceller role is in charge of canceling operations: the Governor instance must be granted this role, -and it MUST be the only canceller in the system. - -Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. - -The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a -fixed quorum value instead of a percentage: - -``` -#[starknet::contract] -pub mod MyTimelockedGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, - GovernorTimelockExecutionComponent - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM: u256 = 100_000_000; - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); - component!( - path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorTimelockExecutionComponent, - storage: governor_timelock_execution, - event: GovernorTimelockExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; - #[abi(embed_v0)] - impl GovernorSettingsAdminImpl = - GovernorSettingsComponent::GovernorSettingsAdminImpl; - #[abi(embed_v0)] - impl TimelockedImpl = - GovernorTimelockExecutionComponent::TimelockedImpl; - - // Extensions internal - impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; - impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorTimelockExecutionImpl = - GovernorTimelockExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesComponent::Storage, - #[substorage(v0)] - pub governor_settings: GovernorSettingsComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesComponent::Event, - #[flat] - GovernorSettingsEvent: GovernorSettingsComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress - ) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token); - self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); - self.governor_timelock_execution.initializer(timelock_controller); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { - /// See `GovernorComponent::GovernorQuorumTrait::quorum`. - fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { - QUORUM - } - } -} -``` - -## Interface - -This is the full interface of the `Governor` implementation: - -``` -#[starknet::interface] -pub trait IGovernor { - fn name(self: @TState) -> felt252; - fn version(self: @TState) -> felt252; - fn COUNTING_MODE(self: @TState) -> ByteArray; - fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; - fn state(self: @TState, proposal_id: felt252) -> ProposalState; - fn proposal_threshold(self: @TState) -> u256; - fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; - fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; - fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; - fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; - fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; - fn voting_delay(self: @TState) -> u64; - fn voting_period(self: @TState) -> u64; - fn quorum(self: @TState, timepoint: u64) -> u256; - fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_votes_with_params( - self: @TState, account: ContractAddress, timepoint: u64, params: Span - ) -> u256; - fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; - fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; - fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; - fn cast_vote_with_reason( - ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray - ) -> u256; - fn cast_vote_with_reason_and_params( - ref self: TState, - proposal_id: felt252, - support: u8, - reason: ByteArray, - params: Span - ) -> u256; - fn cast_vote_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - signature: Span - ) -> u256; - fn cast_vote_with_reason_and_params_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - reason: ByteArray, - params: Span, - signature: Span - ) -> u256; - fn nonces(self: @TState, voter: ContractAddress) -> felt252; - fn relay(ref self: TState, call: Call); -} -``` - -← API Reference - -Multisig → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/governance/multisig - -## Multisig - OpenZeppelin Docs - -# Multisig - -The Multisig component implements a multi-signature mechanism to enhance the security and -governance of smart contract transactions. It ensures that no single signer can unilaterally -execute critical actions, requiring multiple registered signers to approve and collectively -execute transactions. - -This component is designed to secure operations such as fund management or protocol governance, -where collective decision-making is essential. The Multisig Component is self-administered, -meaning that changes to signers or quorum must be approved through the multisig process itself. - -## Key features - -* **Multi-Signature Security**: transactions must be approved by multiple signers, ensuring - distributed governance. -* **Quorum Enforcement**: defines the minimum number of approvals required for transaction execution. -* **Self-Administration**: all modifications to the component (e.g., adding or removing signers) - must pass through the multisig process. -* **Event Logging**: provides comprehensive event logging for transparency and auditability. - -## Signer management - -The Multisig component introduces the concept of signers and quorum: - -* **Signers**: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig - Component supports adding, removing, or replacing signers. -* **Quorum**: the quorum defines the minimum number of confirmations required to approve a transaction. - -| | | -| --- | --- | -| | To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum. This ensures that all modifications pass through the multisig approval process. | - -## Transaction lifecycle - -The state of a transaction is represented by the `TransactionState` enum and can be retrieved -by calling the `get_transaction_state` function with the transaction’s identifier. - -The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash -of the transaction’s calls and salt. It can be computed by invoking the implementing contract’s -`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call -transactions. Submitting a transaction with identical calls and the same salt value a second time -will fail, as transaction identifiers must be unique. To resolve this, use a different salt value -to generate a unique identifier. - -A transaction in the Multisig component follows a specific lifecycle: - -`NotFound` → `Pending` → `Confirmed` → `Executed` - -* **NotFound**: the transaction does not exist. -* **Pending**: the transaction exists but has not reached the required confirmations. -* **Confirmed**: the transaction has reached the quorum but has not yet been executed. -* **Executed**: the transaction has been successfully executed. - -## Usage - -Integrating the Multisig functionality into a contract requires implementing MultisigComponent. -The contract’s constructor should initialize the component with a quorum value and a list of initial signers. - -Here’s an example of a simple wallet contract featuring the Multisig functionality: - -``` -#[starknet::contract] -mod MultisigWallet { - use openzeppelin_governance::multisig::MultisigComponent; - use starknet::ContractAddress; - - component!(path: MultisigComponent, storage: multisig, event: MultisigEvent); - - #[abi(embed_v0)] - impl MultisigImpl = MultisigComponent::MultisigImpl; - impl MultisigInternalImpl = MultisigComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - multisig: MultisigComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - MultisigEvent: MultisigComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, quorum: u32, signers: Span) { - self.multisig.initializer(quorum, signers); - } -} -``` - -## Interface - -This is the interface of a contract implementing the MultisigComponent: - -``` -#[starknet::interface] -pub trait MultisigABI { - // Read functions - fn get_quorum(self: @TState) -> u32; - fn is_signer(self: @TState, signer: ContractAddress) -> bool; - fn get_signers(self: @TState) -> Span; - fn is_confirmed(self: @TState, id: TransactionID) -> bool; - fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool; - fn is_executed(self: @TState, id: TransactionID) -> bool; - fn get_submitted_block(self: @TState, id: TransactionID) -> u64; - fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState; - fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32; - fn hash_transaction( - self: @TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID; - - // Write functions - fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span); - fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span); - fn replace_signer( - ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress, - ); - fn change_quorum(ref self: TState, new_quorum: u32); - fn submit_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn submit_transaction_batch( - ref self: TState, calls: Span, salt: felt252, - ) -> TransactionID; - fn confirm_transaction(ref self: TState, id: TransactionID); - fn revoke_confirmation(ref self: TState, id: TransactionID); - fn execute_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ); - fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252); -} -``` - -← Governor - -Timelock Controller → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/governance/timelock - -## Timelock Controller - OpenZeppelin Docs - -# Timelock Controller - -The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. - -| | | -| --- | --- | -| | The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. | - -## Operation lifecycle - -The state of an operation is represented by the `OperationState` enum and can be retrieved -by calling the `get_operation_state` function with the operation’s identifier. - -The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the -operation’s call or calls, its predecessor, and salt. It can be computed by invoking the -implementing contract’s `hash_operation` function for single-call operations or -`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls, -predecessor, and the same salt value a second time will fail, as operation identifiers must be -unique. To resolve this, use a different salt value to generate a unique identifier. - -Timelocked operations follow a specific lifecycle: - -`Unset` → `Waiting` → `Ready` → `Done` - -* `Unset`: the operation has not been scheduled or has been canceled. -* `Waiting`: the operation has been scheduled and is pending the scheduled delay. -* `Ready`: the timer has expired, and the operation is eligible for execution. -* `Done`: the operation has been executed. - -## Timelock flow - -### Schedule - -When a proposer calls schedule, the `OperationState` moves from `Unset` to `Waiting`. -This starts a timer that must be greater than or equal to the minimum delay. -The timer expires at a timestamp accessible through get\_timestamp. -Once the timer expires, the `OperationState` automatically moves to the `Ready` state. -At this point, it can be executed. - -### Execute - -By calling execute, an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor’s operation must be in the `Done` state for this transaction to succeed. - -### Cancel - -The cancel function allows cancellers to cancel any pending operations. -This resets the operation to the `Unset` state. -It is therefore possible for a proposer to re-schedule an operation that has been cancelled. -In this case, the timer restarts when the operation is re-scheduled. - -### Roles - -TimelockControllerComponent leverages an AccessControlComponent setup that we need to understand in order to set up roles. - -* `PROPOSER_ROLE` - in charge of queueing operations. -* `CANCELLER_ROLE` - may cancel scheduled operations. - During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. - Therefore, the initial proposers may also cancel operations after they are scheduled. -* `EXECUTOR_ROLE` - in charge of executing already available operations. -* `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. - -| | | -| --- | --- | -| | The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. The latter case may be required to ease a contract’s initial configuration; however, this role should promptly be renounced. | - -Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. -This allows anyone to execute an operation once it’s in the `Ready` OperationState. -To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. - -| | | -| --- | --- | -| | Be very careful with enabling open roles as *anyone* can call the function. | - -### Minimum delay - -The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. -The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. - -After initialization, the only way to change the timelock’s minimum delay is to schedule it and execute it with the same flow as any other operation. - -The minimum delay of a contract is accessible through get\_min\_delay. - -### Usage - -Integrating the timelock into a contract requires integrating TimelockControllerComponent as well as SRC5Component and AccessControlComponent as dependencies. -The contract’s constructor should initialize the timelock which consists of setting the: - -* Proposers and executors. -* Minimum delay between scheduling and executing an operation. -* Optional admin if additional configuration is required. - -| | | -| --- | --- | -| | The optional admin should renounce their role once configuration is complete. | - -Here’s an example of a simple timelock contract: - -``` -#[starknet::contract] -mod TimelockControllerContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_governance::timelock::TimelockControllerComponent; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ContractAddress; - - component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); - component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Timelock Mixin - #[abi(embed_v0)] - impl TimelockMixinImpl = - TimelockControllerComponent::TimelockMixinImpl; - impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - access_control: AccessControlComponent::Storage, - #[substorage(v0)] - timelock: TimelockControllerComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - TimelockEvent: TimelockControllerComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - min_delay: u64, - proposers: Span, - executors: Span, - admin: ContractAddress - ) { - self.timelock.initializer(min_delay, proposers, executors, admin); - } -} -``` - -### Interface - -This is the full interface of the TimelockMixinImpl implementation: - -``` -#[starknet::interface] -pub trait TimelockABI { - // ITimelock - fn is_operation(self: @TState, id: felt252) -> bool; - fn is_operation_pending(self: @TState, id: felt252) -> bool; - fn is_operation_ready(self: @TState, id: felt252) -> bool; - fn is_operation_done(self: @TState, id: felt252) -> bool; - fn get_timestamp(self: @TState, id: felt252) -> u64; - fn get_operation_state(self: @TState, id: felt252) -> OperationState; - fn get_min_delay(self: @TState) -> u64; - fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; - fn hash_operation_batch( - self: @TState, calls: Span, predecessor: felt252, salt: felt252 - ) -> felt252; - fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); - fn schedule_batch( - ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 - ); - fn cancel(ref self: TState, id: felt252); - fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); - fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); - fn update_delay(ref self: TState, new_delay: u64); - - // ISRC5 - fn supports_interface(self: @TState, interface_id: felt252) -> bool; - - // IAccessControl - fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(self: @TState, role: felt252) -> felt252; - fn grant_role(ref self: TState, role: felt252, account: ContractAddress); - fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); - fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(self: @TState, role: felt252) -> felt252; - fn grantRole(ref self: TState, role: felt252, account: ContractAddress); - fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); - fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); -} -``` - -← Multisig - -Votes → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/governance/votes - -## Votes - OpenZeppelin Docs - -# Votes - -The VotesComponent provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. - -| | | -| --- | --- | -| | By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. | - -| | | -| --- | --- | -| | The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the usage section for examples of how to implement this. | - -## Key features - -1. **Delegation**: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the delegate function directly, or by providing a signature to be used with delegate\_by\_sig. -2. **Historical lookups**: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp. - This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. - -## Usage - -When integrating the `VotesComponent`, the VotingUnitsTrait must be implemented to get the voting units for a given account as a function of the implementing contract. -For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated. -Additionally, you must implement the NoncesComponent and the SNIP12Metadata trait to enable delegation by signatures. - -Here’s an example of how to structure a simple ERC20Votes contract: - -``` -#[starknet::contract] -mod ERC20VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc20_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20VotesEvent: VotesComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - // Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. - impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { - fn after_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let mut contract_state = self.get_contract_mut(); - contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc20.initializer("MyToken", "MTK"); - } -} -``` - -And here’s an example of how to structure a simple ERC721Votes contract: - -``` -#[starknet::contract] -pub mod ERC721VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721Component; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC721 - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc721_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc721: ERC721Component::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721VotesEvent: VotesComponent::Event, - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - /// Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `before_update` hook of the - //`ERC721Component::ERC721HooksTrait`. - // This hook is called before the transfer is executed. - // This gives us access to the previous owner. - impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { - fn before_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) { - let mut contract_state = self.get_contract_mut(); - - // We use the internal function here since it does not check if the token - // id exists which is necessary for mints - let previous_owner = self._owner_of(token_id); - contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721.initializer("MyToken", "MTK", ""); - } -} -``` - -## Interface - -This is the full interface of the `VotesImpl` implementation: - -``` -#[starknet::interface] -pub trait VotesABI { - // IVotes - fn get_votes(self: @TState, account: ContractAddress) -> u256; - fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; - fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; - fn delegate(ref self: TState, delegatee: ContractAddress); - fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); - - // INonces - fn nonces(self: @TState, owner: ContractAddress) -> felt252; -} -``` - -← Timelock Controller - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/deploy-udc - -## UDC Appchain Deployment - OpenZeppelin Docs - -# UDC Appchain Deployment - -While the Universal Deployer Contract (UDC) is deployed on Starknet public networks, appchains may need to deploy -their own instance of the UDC for their own use. This guide will walk you through this process while keeping the -same final address on all networks. - -## Prerequisites - -This guide assumes you have: - -* Familiarity with Scarb and Starknet development environment. -* A functional account available on the network you’re deploying to. -* Familiarity with the process of declaring contracts through the declare transaction. - -| | | -| --- | --- | -| | For declaring contracts on Starknet, you can use the sncast tool from the starknet-foundry project. | - -## Note on the UDC final address - -It is important that the Universal Deployer Contract (UDC) in Starknet maintains the same address across all -networks because essential developer tools like **starkli** and **sncast** rely on this address by default when deploying contracts. -These tools are widely used in the Starknet ecosystem to streamline and standardize contract deployment workflows. - -If the UDC address is consistent, developers can write deployment scripts, CI/CD pipelines, and integrations that work seamlessly -across testnets, mainnet, and appchains without needing to update configuration files or handle special cases for each -environment. - -In the following sections, we’ll walk you through the process of deploying the UDC on appchains while keeping the same address, -under one important assumption: **the declared UDC class hash MUST be the same across all networks**. -Different compiler versions may produce different class hashes for the same contract, so you need to make -sure you are using the same compiler version to build the UDC class (and the release profile). - -The latest version of the UDC available in the `openzeppelin_presets` package was compiled with **Cairo v2.11.4** (release profile) and the resulting class hash is `0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. - -| | | -| --- | --- | -| | If you are using a different compiler version, you need to make sure the class hash is the same as the one above in order to keep the same address across all networks. | - -| | | -| --- | --- | -| | To avoid potential issues by using a different compiler version, you can directly import the contract class deployed on Starknet mainnet and declare it on your appchain. At the time of writing, this is not easily achievable with the `sncast` tool, but you can leverage `starkli` to do it. Quick reference: ``` starkli class-by-hash --parse \ 0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8 \ --network mainnet \ > udc.json ``` This will output a `udc.json` file that you can use to declare the UDC on your appchain. ``` starkli declare udc.json --rpc ``` | - -## Madara Appchains - -Madara is a popular Starknet node implementation that has a friendly and robust interface for building appchains. If -you are using it for this purpose, you are probably familiar with the Madara Bootstrapper, which already declares and -deploys a few contracts for you when you create a new appchain, including accounts and the UDC. - -However, since the UDC was migrated to a new version in June 2025, it’s possible that the appchain was created before -this change, meaning the UDC on the appchain is an older version. If that’s the case, you can follow the steps below to -deploy the new UDC. - -### 1. Declare and deploy the Bootstrapper - -In the Starknet ecosystem, contracts need to be declared before they can be deployed, and deployments can only happen -either via the `deploy_syscall`, or using a `deploy_account` transaction. The latter would require adding account -functionality to the UDC, which is not optimal, so we’ll use the `deploy_syscall`, which requires having an account -with this functionality enabled. - -| | | -| --- | --- | -| | Madara declares an account with this functionality enabled as part of the bootstrapping process. You may be able to use that implementation directly to skip this step. | - -#### Bootstrapper Contract - -The bootstrapper contract is a simple contract that declares the UDC and allows for its deployment via the `deploy_syscall`. -You can find a reference implementation below: - -| | | -| --- | --- | -| | This reference implementation targets Cairo v2.11.4. If you are using a different version of Cairo, you may need to update the code to match your compiler version. | - -``` -#[starknet::contract(account)] -mod UniversalDeployerBootstrapper { - use core::num::traits::Zero; - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::deployments::calculate_contract_address_from_deploy_syscall; - use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // - // Account features (deployable, declarer, and invoker) - // - - #[abi(embed_v0)] - pub(crate) impl DeployableImpl = - AccountComponent::DeployableImpl; - #[abi(embed_v0)] - impl DeclarerImpl = AccountComponent::DeclarerImpl; - #[abi(embed_v0)] - impl SRC6Impl = AccountComponent::SRC6Impl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub account: AccountComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - pub(crate) enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - pub fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } - - #[abi(per_item)] - #[generate_trait] - impl ExternalImpl of ExternalTrait { - #[external(v0)] - fn deploy_udc(ref self: ContractState, udc_class_hash: ClassHash) { - self.account.assert_only_self(); - starknet::syscalls::deploy_syscall(udc_class_hash, 0, array![].span(), true) - .unwrap_syscall(); - } - - #[external(v0)] - fn get_udc_address(ref self: ContractState, udc_class_hash: ClassHash) -> ContractAddress { - calculate_contract_address_from_deploy_syscall( - 0, udc_class_hash, array![].span(), Zero::zero(), - ) - } - } -} -``` - -#### Deploying the Bootstrapper - -This guide assumes you have a functional account available on the network you’re deploying to, and familiarity -with the process of declaring contracts through the `declare` transaction. To recap, the reason we are deploying -this bootstrapper account contract is to be able to deploy the UDC via the `deploy_syscall`. - -| | | -| --- | --- | -| | sncast v0.45.0 was used in the examples below. | - -As a quick example, if your account is configured for **sncast**, you can declare the bootstrapper contract with the following command: - -``` -sncast -p declare \ - --contract-name UniversalDeployerBootstrapper -``` - -The bootstrapper implements the `IDeployable` trait, meaning it can be counterfactually deployed. Check out the -Counterfactual Deployments guide. Continuing with the **sncast** examples, you can create and deploy the bootstrapper with the following commands: - -##### Create the account - -``` -sncast account create --name bootstrapper \ - --network \ - --class-hash \ - --type oz -``` - -##### Deploy it to the network - -| | | -| --- | --- | -| | You need to prefund the account with enough funds before you can deploy it. | - -``` -sncast account deploy \ - --network \ - --name bootstrapper -``` - -### 2. Declare and deploy the UDC - -Once the bootstrapper is deployed, you can declare and deploy the UDC through it. - -#### Declaring the UDC - -The UDC source code is available in the `openzeppelin_presets` package. You can copy it to your project and declare it with the following command: - -``` -sncast -p declare \ - --contract-name UniversalDeployer -``` - -| | | -| --- | --- | -| | If you followed the Note on the UDC final address section, your declared class hash should be `0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. | - -#### Previewing the UDC address - -You can preview the UDC address with the following command: - -``` -sncast call \ - --network \ - --contract-address \ - --function "get_udc_address" \ - --arguments '' -``` - -If the UDC class hash is the same as the one in the Note on the UDC final address section, -the output should be `0x2ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125`. - -#### Deploying the UDC - -Now everything is set up to deploy the UDC. You can use the following command to deploy it: - -| | | -| --- | --- | -| | Note that the bootstrapper contract MUST call itself to successfully deploy the UDC, since the `deploy_udc` function is protected. | - -``` -sncast \ - --account bootstrapper \ - invoke \ - --network \ - --contract-address \ - --function "deploy_udc" \ - --arguments '' -``` - -## Other Appchain providers - -If you are using an appchain provider different from Madara, you can follow the same steps to deploy the UDC -as long as you have access to an account that can declare contracts. - -Summarizing, the steps to follow are: - -1. Declare the Bootstrapper -2. Counterfactually deploy the Bootstrapper -3. Declare the UDC -4. Preview the UDC address -5. Deploy the UDC from the Bootstrapper - -## Conclusion - -By following this guide, you have successfully deployed the Universal Deployer Contract on your appchain while ensuring consistency with -Starknet’s public networks. Maintaining the same UDC address and class hash across all environments is crucial for seamless contract deployment -and tooling compatibility, allowing developers to leverage tools like **sncast** and **starkli** without additional configuration. This process not only -improves the reliability of your deployment workflows but also ensures that your appchain remains compatible with the broader Starknet ecosystem. -With the UDC correctly deployed, you are now ready to take full advantage of streamlined contract -deployments and robust developer tooling on your appchain. - -← Universal Deployer Contract - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/deployment - -## Counterfactual deployments - OpenZeppelin Docs - -# Counterfactual deployments - -A counterfactual contract is a contract we can interact with even before actually deploying it on-chain. -For example, we can send funds or assign privileges to a contract that doesn’t yet exist. -Why? Because deployments in Starknet are deterministic, allowing us to predict the address where our contract will be deployed. -We can leverage this property to make a contract pay for its own deployment by simply sending funds in advance. We call this a counterfactual deployment. - -This process can be described with the following steps: - -| | | -| --- | --- | -| | For testing this flow you can check the Starknet Foundry or the Starkli guides for deploying accounts. | - -1. Deterministically precompute the `contract_address` given a `class_hash`, `salt`, and constructor `calldata`. - Note that the `class_hash` must be previously declared for the deployment to succeed. -2. Send funds to the `contract_address`. Usually you will estimate the fee of the transaction first. Existing - tools usually do this for you. -3. Send a `DeployAccount` type transaction to the network. -4. The protocol will then validate the transaction with the `__validate_deploy__` entrypoint of the contract to be deployed. -5. If the validation succeeds, the protocol will charge the fee and then register the contract as deployed. - -| | | -| --- | --- | -| | Although this method is very popular to deploy accounts, this works for any kind of contract. | - -## Deployment validation - -To be counterfactually deployed, the deploying contract must implement the `__validate_deploy__` entrypoint, -called by the protocol when a `DeployAccount` transaction is sent to the network. - -``` -trait IDeployable { - /// Must return 'VALID' when the validation is successful. - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; -} -``` - -← Interfaces and Dispatchers - -SNIP12 and Typed Messages → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/erc20-permit - -## ERC20Permit - OpenZeppelin Docs - -# ERC20Permit - -The EIP-2612 standard, commonly referred to as ERC20Permit, is designed to support gasless token approvals. This is achieved with an off-chain -signature following the SNIP12 standard, rather than with an on-chain transaction. The permit function verifies the signature and sets -the spender’s allowance if the signature is valid. This approach improves user experience and reduces gas costs. - -## Differences from Solidity - -Although this extension is mostly similar to the Solidity implementation of EIP-2612, there are some notable differences in the parameters of the permit function: - -* The `deadline` parameter is represented by `u64` rather than `u256`. -* The `signature` parameter is represented by a span of felts rather than `v`, `r`, and `s` values. - -| | | -| --- | --- | -| | Unlike Solidity, there is no enforced format for signatures on Starknet. A signature is represented by an array or span of felts, and there is no universal method for validating signatures of unknown formats. Consequently, a signature provided to the permit function is validated through an external `is_valid_signature` call to the contract at the `owner` address. | - -## Usage - -The functionality is provided as an embeddable ERC20Permit trait of the ERC20Component. - -``` -#[abi(embed_v0)] -impl ERC20PermitImpl = ERC20Component::ERC20PermitImpl; -``` - -A contract must meet the following requirements to be able to use the ERC20Permit trait: - -* Implement ERC20Component. -* Implement NoncesComponent. -* Implement SNIP12Metadata trait (used in signature generation). - -## Typed message - -To safeguard against replay attacks and ensure the uniqueness of each approval via permit, the data signed includes: - -* The address of the `owner`. -* The parameters specified in the approve function (`spender` and `amount`) -* The address of the `token` contract itself. -* A `nonce`, which must be unique for each operation. -* The `chain_id`, which protects against cross-chain replay attacks. - -The format of the `Permit` structure in a signed permit message is as follows: - -``` -struct Permit { - token: ContractAddress, - spender: ContractAddress, - amount: u256, - nonce: felt252, - deadline: u64, -} -``` - -| | | -| --- | --- | -| | The owner of the tokens is also part of the signed message. It is used as the `signer` parameter in the `get_message_hash` call. | - -Further details on preparing and signing a typed message can be found in the SNIP12 guide. - -← Creating Supply - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/erc20-supply - -## Creating ERC20 Supply - OpenZeppelin Docs - -# Creating ERC20 Supply - -The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. -EIP20, from which ERC20 contracts are derived, does not specify how tokens are created. -This guide will go over strategies for creating both a fixed and dynamic token supply. - -## Fixed Supply - -Let’s say we want to create a token named `MyToken` with a fixed token supply. -We can achieve this by setting the token supply in the constructor which will execute upon deployment. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - fixed_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -In the constructor, we’re first calling the ERC20 initializer to set the token name and symbol. -Next, we’re calling the internal `mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. -Since the internal `mint` is not exposed in our contract, it will not be possible to create any more tokens. -In other words, we’ve implemented a fixed token supply! - -## Dynamic Supply - -ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. -Let’s make a few changes to the almighty `MyToken` contract and create a minting mechanism. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - } - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // This function is NOT protected which means - // ANYONE can mint tokens - self.erc20.mint(recipient, amount); - } -} -``` - -The exposed `mint` above will create `amount` tokens and allocate them to `recipient`. -We now have our minting mechanism! - -There is, however, a big problem. -`mint` does not include any restrictions on who can call this function. -For the sake of good practices, let’s implement a simple permissioning mechanism with `Ownable`. - -``` -#[starknet::contract] -mod MyToken { - - (...) - - // Integrate Ownable - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // Set permissions with Ownable - self.ownable.assert_only_owner(); - - // Mint tokens if called by the contract owner - self.erc20.mint(recipient, amount); - } -} -``` - -In the constructor, we pass the owner address to set the owner of the `MyToken` contract. -The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function. -Now, we have a protected ERC20 minting mechanism to create a dynamic token supply. - -| | | -| --- | --- | -| | For a more thorough explanation of permission mechanisms, see Access Control. | - -← ERC20 - -ERC20Permit → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/snip12 - -## SNIP12 and Typed Messages - OpenZeppelin Docs - -# SNIP12 and Typed Messages - -Similar to EIP712, SNIP12 is a standard for secure off-chain signature verification on Starknet. -It provides a way to hash and sign generic typed structs rather than just strings. When building decentralized -applications, usually you might need to sign a message with complex data. The purpose of signature verification -is then to ensure that the received message was indeed signed by the expected signer, and it hasn’t been tampered with. - -OpenZeppelin Contracts for Cairo provides a set of utilities to make the implementation of this standard -as easy as possible, and in this guide we will walk you through the process of generating the hashes of typed messages -using these utilities for on-chain signature verification. For that, let’s build an example with a custom ERC20 contract -adding an extra `transfer_with_signature` method. - -| | | -| --- | --- | -| | This is an educational example, and it is not intended to be used in production environments. | - -## CustomERC20 - -Let’s start with a basic ERC20 contract leveraging the ERC20Component, and let’s add the new function. -Note that some declarations are omitted for brevity. The full example will be available at the end of the guide. - -``` -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - (...) - } -} -``` - -The `transfer_with_signature` function will allow a user to transfer tokens to another account by providing a signature. -The signature will be generated off-chain, and it will be used to verify the message on-chain. Note that the message -we need to verify is a struct with the following fields: - -* `recipient`: The address of the recipient. -* `amount`: The amount of tokens to transfer. -* `nonce`: A unique number to prevent replay attacks. -* `expiry`: The timestamp when the signature expires. - -Note that generating the hash of this message on-chain is a requirement to verify the signature, because if we accept -the message as a parameter, it could be easily tampered with. - -## Generating the Typed Message Hash - -To generate the hash of the message, we need to follow these steps: - -### 1. Define the message struct. - -In this particular example, the message struct looks like this: - -``` -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} -``` - -### 2. Get the message type hash. - -This is the `starknet_keccak(encode_type(message))` as defined in the SNIP. - -In this case it can be computed as follows: - -``` -// Since there's no u64 type in SNIP-12, we use u128 for `expiry` in the type hash generation. -let message_type_hash = selector!( - "\"Message\"(\"recipient\":\"ContractAddress\",\"amount\":\"u256\",\"nonce\":\"felt\",\"expiry\":\"u128\")\"u256\"(\"low\":\"u128\",\"high\":\"u128\")" -); -``` - -which is the same as: - -``` -let message_type_hash = 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; -``` - -| | | -| --- | --- | -| | In practice it’s better to compute the type hash off-chain and hardcode it in the contract, since it is a constant value. | - -### 3. Implement the `StructHash` trait for the struct. - -You can import the trait from: `openzeppelin_utils::snip12::StructHash`. And this implementation -is nothing more than the encoding of the message as defined in the SNIP. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::StructHash; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} -``` - -### 4. Implement the `SNIP12Metadata` trait. - -This implementation determines the values of the domain separator. Only the `name` and `version` fields are required -because the `chain_id` is obtained on-chain, and the `revision` is hardcoded to `1`. - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { 'DAPP_NAME' } - fn version() -> felt252 { 'v1' } -} -``` - -In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in -some cases we may need to read from storage to get the domain separator values. This can be accomplished even when -the trait is not bounded to the ContractState, like this: - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - let state = unsafe_new_contract_state(); - - // Some logic to get the name from storage - state.erc20.name().at(0).unwrap().into() - } - - fn version() -> felt252 { 'v1' } -} -``` - -### 5. Generate the hash. - -The final step is to use the `OffchainMessageHashImpl` implementation to generate the hash of the message -using the `get_message_hash` function. The implementation is already available as a utility. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'v1' - } -} - -fn get_hash( - account: ContractAddress, recipient: ContractAddress, amount: u256, nonce: felt252, expiry: u64 -) -> felt252 { - let message = Message { recipient, amount, nonce, expiry }; - message.get_message_hash(account) -} -``` - -| | | -| --- | --- | -| | The expected parameter for the `get_message_hash` function is the address of account that signed the message. | - -## Full Implementation - -Finally, the full implementation of the `CustomERC20` contract looks like this: - -| | | -| --- | --- | -| | We are using the `ISRC6Dispatcher` to verify the signature, and the `NoncesComponent` to handle nonces to prevent replay attacks. | - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use starknet::ContractAddress; - - use super::{Message, OffchainMessageHash, SNIP12Metadata}; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - impl NoncesInternalImpl = NoncesComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - /// Required for hash computation. - impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'CustomERC20' - } - fn version() -> felt252 { - 'v1' - } - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - assert(starknet::get_block_timestamp() <= expiry, 'Expired signature'); - let owner = starknet::get_caller_address(); - - // Check and increase nonce - self.nonces.use_checked_nonce(owner, nonce); - - // Build hash for calling `is_valid_signature` - let message = Message { recipient, amount, nonce, expiry }; - let hash = message.get_message_hash(owner); - - let is_valid_signature_felt = ISRC6Dispatcher { contract_address: owner } - .is_valid_signature(hash, signature); - - // Check either 'VALID' or true for backwards compatibility - let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED - || is_valid_signature_felt == 1; - assert(is_valid_signature, 'Invalid signature'); - - // Transfer tokens - self.erc20._transfer(owner, recipient, amount); - } -} -``` - -← Counterfactual Deployments - -Access → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/guides/src5-migration - -## Migrating ERC165 to SRC5 - OpenZeppelin Docs - -# Migrating ERC165 to SRC5 - -In the smart contract ecosystem, having the ability to query if a contract supports a given interface is an extremely important feature. -The initial introspection design for Contracts for Cairo before version v0.7.0 followed Ethereum’s EIP-165. -Since the Cairo language evolved introducing native types, we needed an introspection solution tailored to the Cairo ecosystem: the SNIP-5 standard. -SNIP-5 allows interface ID calculations to use Cairo types and the Starknet keccak (`sn_keccak`) function. -For more information on the decision, see the Starknet Shamans proposal or the Dual Introspection Detection discussion. - -## How to migrate - -Migrating from ERC165 to SRC5 involves four major steps: - -1. Integrate SRC5 into the contract. -2. Register SRC5 IDs. -3. Add a `migrate` function to apply introspection changes. -4. Upgrade the contract and call `migrate`. - -The following guide will go through the steps with examples. - -### Component integration - -The first step is to integrate the necessary components into the new contract. -The contract should include the new introspection mechanism, SRC5Component. -It should also include the InitializableComponent which will be used in the `migrate` function. -Here’s the setup: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_security::initializable::InitializableComponent; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - // Initializable - impl InitializableInternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -### Interface registration - -To successfully migrate ERC165 to SRC5, the contract needs to register the interface IDs that the contract supports with SRC5. - -For this example, let’s say that this contract supports the IERC721 and IERC721Metadata interfaces. -The contract should implement an `InternalImpl` and add a function to register those interfaces like this: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID}; - - (...) - - #[generate_trait] - impl InternalImpl of InternalTrait { - // Register SRC5 interfaces - fn register_src5_interfaces(ref self: ContractState) { - self.src5.register_interface(IERC721_ID); - self.src5.register_interface(IERC721_METADATA_ID); - } - } -} -``` - -Since the new contract integrates `SRC5Component`, it can leverage SRC5’s register\_interface function to register the supported interfaces. - -### Migration initializer - -Next, the contract should define and expose a migration function that will invoke the `register_src5_interfaces` function. -Since the `migrate` function will be publicly callable, it should include some sort of Access Control so that only permitted addresses can execute the migration. -Finally, `migrate` should include a reinitialization check to ensure that it cannot be called more than once. - -| | | -| --- | --- | -| | If the original contract implemented `Initializable` at any point and called the `initialize` method, the `InitializableComponent` will not be usable at this time. Instead, the contract can take inspiration from `InitializableComponent` and create its own initialization mechanism. | - -``` -#[starknet::contract] -mod MigratingContract { - (...) - - #[external(v0)] - fn migrate(ref self: ContractState) { - // WARNING: Missing Access Control mechanism. Make sure to add one - - // WARNING: If the contract ever implemented `Initializable` in the past, - // this will not work. Make sure to create a new initialization mechanism - self.initializable.initialize(); - - // Register SRC5 interfaces - self.register_src5_interfaces(); - } -} -``` - -### Execute migration - -Once the new contract is prepared for migration and **rigorously tested**, all that’s left is to migrate! -Simply upgrade the contract and then call `migrate`. - -← Introspection - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/interfaces - -## Interfaces and Dispatchers - OpenZeppelin Docs - -# Interfaces and Dispatchers - -This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. - -Interfaces can be found in the module tree under the `interface` submodule, such as `token::erc20::interface`. For example: - -``` -use openzeppelin_token::erc20::interface::IERC20; -``` - -or - -``` -use openzeppelin_token::erc20::interface::ERC20ABI; -``` - -| | | -| --- | --- | -| | For simplicity, we’ll use ERC20 as example but the same concepts apply to other modules. | - -## Interface traits - -The library offers three types of traits to implement or interact with contracts: - -### Standard traits - -These are associated with a predefined interface such as a standard. -This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. - -``` -#[starknet::interface] -pub trait IERC20 { - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### ABI traits - -They describe a contract’s complete interface. This is useful to interface with a preset contract offered by this library, such as the ERC20 preset that includes functions from different traits such as `IERC20` and `IERC20Camel`. - -| | | -| --- | --- | -| | The library offers an ABI trait for most components, providing all external function signatures even when most of the time all of them don’t need to be implemented at the same time. This can be helpful when interacting with a contract implementing the component, instead of defining a new dispatcher. | - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - - // IERC20CamelOnly - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -### Dispatcher traits - -Traits annotated with `#[starknet::interface]` automatically generate a dispatcher that can be used to interact with contracts that implement the given interface. They can be imported by appending the `Dispatcher` and `DispatcherTrait` suffixes to the trait name, like this: - -``` -use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -``` - -Other types of dispatchers are also auto-generated from the annotated trait. See the -Interacting with another contract section of the Cairo book for more information. - -| | | -| --- | --- | -| | In the example, the `IERC20Dispatcher` is the one used to interact with contracts, but the `IERC20DispatcherTrait` needs to be in scope for the functions to be available. | - -## Dual interfaces - -| | | -| --- | --- | -| | `camelCase` functions are deprecated and maintained only for Backwards Compatibility. It’s recommended to only use `snake_case` interfaces with contracts and components. The `camelCase` functions will be removed in future versions. | - -Following the Great Interface Migration plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. - -In short, the library offers two types of interfaces and utilities to handle them: - -1. `camelCase` interfaces, which are the ones we’ve been using so far. -2. `snake_case` interfaces, which are the ones we’re migrating to. - -This means that currently most of our contracts implement *dual interfaces*. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. - -| | | -| --- | --- | -| | Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo (v0.6.1 and below). | - -### `IERC20` - -The default version of the ERC20 interface trait exposes `snake_case` functions: - -``` -#[starknet::interface] -pub trait IERC20 { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### `IERC20Camel` - -On top of that, the library also offers a `camelCase` version of the same interface: - -``` -#[starknet::interface] -pub trait IERC20Camel { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -← Presets - -Counterfactual Deployments → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/introspection - -## Introspection - OpenZeppelin Docs - -# Introspection - -To smooth interoperability, often standards require smart contracts to implement introspection mechanisms. - -In Ethereum, the EIP165 standard defines how contracts should declare -their support for a given interface, and how other contracts may query this support. - -Starknet offers a similar mechanism for interface introspection defined by the SRC5 standard. - -## SRC5 - -Similar to its Ethereum counterpart, the SRC5 standard requires contracts to implement the `supports_interface` function, -which can be used by others to query if a given interface is supported. - -### Usage - -To expose this functionality, the contract must implement the SRC5Component, which defines the `supports_interface` function. -Here is an example contract: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.src5.register_interface(selector!("some_interface")); - } -} -``` - -### Interface - -``` -#[starknet::interface] -pub trait ISRC5 { - /// Query if a contract implements an interface. - /// Receives the interface identifier as specified in SRC-5. - /// Returns `true` if the contract implements `interface_id`, `false` otherwise. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -## Computing the interface ID - -The interface ID, as specified in the standard, is the XOR of all the -Extended Function Selectors -of the interface. We strongly advise reading the SNIP to understand the specifics of computing these -extended function selectors. There are tools such as src5-rs that can help with this process. - -## Registering interfaces - -For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - // Register the contract's support for the ISRC6 interface - self.src5.register_interface(interface::ISRC6_ID); - } - - (...) -} -``` - -## Querying interfaces - -Use the `supports_interface` function to query a contract’s support for a given interface. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::interface::ISRC5DispatcherTrait; - use openzeppelin_introspection::interface::ISRC5Dispatcher; - use starknet::ContractAddress; - - #[storage] - struct Storage {} - - #[external(v0)] - fn query_is_account(self: @ContractState, target: ContractAddress) -> bool { - let dispatcher = ISRC5Dispatcher { contract_address: target }; - dispatcher.supports_interface(interface::ISRC6_ID) - } -} -``` - -| | | -| --- | --- | -| | If you are unsure whether a contract implements SRC5 or not, you can follow the process described in here. | - -← API Reference - -Migrating ERC165 to SRC5 → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/macros - -## Macros - OpenZeppelin Docs - -# Macros - -This crate provides a collection of macros that streamline and simplify development with the library. -To use them, you need to add the `openzeppelin_macros` crate as a dependency in your `Scarb.toml` file: - -``` -[dependencies] -openzeppelin_macros = "2.0.0" -``` - -## Attribute macros - -* with\_components -* type\_hash - -← API Reference - -with\_components → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/macros/type_hash - -## type_hash - OpenZeppelin Docs - -# `type_hash` - -This macro generates a SNIP-12-compatible type hash for a given struct or enum. - -| | | -| --- | --- | -| | This macro is fully compatible with the SNIP-12 standard revision 1. | - -## Usage - -``` -/// name and debug are optional arguments -#[type_hash(name: "My Struct", debug: true)] -struct MyStruct { - #[snip12(name: "My Field")] - my_field: felt252, -} -``` - -This will generate a type hash for the struct. - -``` -// Encoded type: "My Struct"("My Field":"felt") -pub const MY_STRUCT_TYPE_HASH: felt252 = 0x1735aa9819941b96c651b740b792a96c854565eaff089b7e293d996828b88a8; -``` - -And because of the `debug` argument, it will generate the following code: - -``` -pub fn __MY_STRUCT_encoded_type() { - println!("\"My Struct\"(\"My Field\":\"felt\")"); -} -``` - -## Basic types - -The list of supported basic types as defined in the SNIP-12 standard is: - -* felt252 -* shortstring -* ClassHash -* ContractAddress -* timestamp -* selector -* merkletree -* u128 -* i128 - -### Examples - -Struct with basic types and custom names and kinds: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Simple Felt")] // Optional custom name - pub simple_felt: felt252, - #[snip12(name: "Class Hash")] - pub class_hash: ClassHash, - #[snip12(name: "Target Token")] - pub target: ContractAddress, - #[snip12(name: "Timestamp", kind: "timestamp")] - pub timestamp: u128, - #[snip12(name: "Selector", kind: "selector")] - pub selector: felt252, -} - -// Encoded type: "My Struct"("Simple Felt":"felt","Class Hash":"ClassHash", -// "Target Token":"ContractAddress","Timestamp":"timestamp","Selector":"selector") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x522e0c3dc5e13b0978f4645760a436b1e119fd335842523fee8fbae6057b8c; -``` - -Enum with basic types and custom names and kinds: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Simple Felt")] - SimpleFelt: felt252, - #[snip12(name: "Class Hash")] - ClassHash: ClassHash, - #[snip12(name: "Target Token")] - ContractAddress: ContractAddress, - #[snip12(name: "Timestamp", kind: "timestamp")] - Timestamp: u128, - #[snip12(name: "Selector", kind: "selector")] - Selector: felt252, -} - -// Encoded type: "My Enum"("Simple Felt"("felt"),"Class Hash"("ClassHash"), -// "Target Token"("ContractAddress"),"Timestamp"("timestamp"),"Selector"("selector")) -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x3f30aaa6cda9f699d4131940b10602b78b986feb88f28a19f3b48567cb4b566; -``` - -## Collection types - -The list of supported collection types as defined in the SNIP-12 standard is: - -* Array -* Tuple **(Only supported for enums)** -* Span **(Treated as an array)** - -| | | -| --- | --- | -| | While Span is not directly supported by the SNIP-12 standard, it is treated as an array for the purposes of this macro, since it is sometimes helpful to use `Span` instead of `Array` in order to save on gas. | - -### Examples - -Struct with collection types: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Member 1")] - pub member1: Array, - #[snip12(name: "Member 2")] - pub member2: Span, - #[snip12(name: "Timestamps", kind: "Array")] - pub timestamps: Array, -} - -// Encoded type: "My Struct"("Member 1":"felt*","Member 2":"u128*", -// "Timestamps":"timestamp*") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x369cdec45d8c55e70986aed44da0e330375171ba6e25b58e741c0ce02fa8ac; -``` - -Enum with collection types: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Member 1")] - Member1: Array, - #[snip12(name: "Member 2")] - Member2: Span, - #[snip12(name: "Timestamps", kind: "Array")] - Timestamps: Array, - #[snip12(name: "Name and Last Name", kind: "(shortstring, shortstring)")] - NameAndLastName: (felt252, felt252), -} - -// Encoded type: "My Enum"("Member 1"("felt*"),"Member 2"("u128*"), -// "Timestamps"("timestamp*"),"Name and Last Name"("shortstring","shortstring")) -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x9e3e1ebad4448a8344b3318f9cfda5df237588fd8328e1c2968635f09c735d; -``` - -## Preset types - -The list of supported preset types as defined in the SNIP-12 standard is: - -* TokenAmount -* NftId -* u256 - -### Examples - -Struct with preset types: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Token Amount")] - pub token_amount: TokenAmount, - #[snip12(name: "NFT ID")] - pub nft_id: NftId, - #[snip12(name: "Number")] - pub number: u256, -} - -// Encoded type: "My Struct"("Token Amount":"TokenAmount","NFT ID":"NftId","Number":"u256")"NftId" -// ("collection_address":"ContractAddress","token_id":"u256")"TokenAmount" -// ("token_address":"ContractAddress","amount":"u256") -// "u256"("low":"u128","high":"u128") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x19f63528d68c4f44b7d9003a5a6b7793f5bb6ffc8a22bdec82b413ddf4f9412; -``` - -Enum with preset types: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Token Amount")] - TokenAmount: TokenAmount, - #[snip12(name: "NFT ID")] - NftId: NftId, - #[snip12(name: "Number")] - Number: u256, -} - -// Encoded type: "My Enum"("Token Amount"("TokenAmount"),"NFT ID"("NftId"),"Number"("u256"))"NftId" -// ("collection_address":"ContractAddress","token_id":"u256")"TokenAmount" -// ("token_address":"ContractAddress","amount":"u256") -// "u256"("low":"u128","high":"u128") -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x39dd19c7e5c5f89e084b78a26200b712c6ae3265f2bae774471c588858421b7; -``` - -## User-defined types - -User-defined types are currently **NOT SUPPORTED** since the macro doesn’t have access to scope outside of the -target struct/enum. In the future it may be supported by extending the syntax to explicitly declare the custom type -definition. - -← with\_components - -Merkle Tree → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/macros/with_components - -## with_components - OpenZeppelin Docs - -# `with_components` - -This macro simplifies the syntax for adding a set of components to a contract. It: - -* *Imports the corresponding components into the contract.* -* *Adds the corresponding `component!` macro entries.* -* *Adds the storage entries for each component to the Storage struct.* -* *Adds the event entries for each component to the Event struct, or creates the struct if it is missing.* -* *Brings the corresponding internal implementations into scope.* -* *Provides some diagnostics for each specific component to help the developer avoid common mistakes.* - -| | | -| --- | --- | -| | Since the macro does not expose any external implementations, developers must make sure to specify explicitly the ones required by the contract. | - -## Security considerations - -The macro was designed to be simple and effective while still being very hard to misuse. For this reason, the features -that it provides are limited, and things that might make the contract behave in unexpected ways must be -explicitly specified by the developer. It does not specify external implementations, so contracts won’t find -themselves in a situation where external functions are exposed without the developer’s knowledge. It brings -the internal implementations into scope so these functions are available by default, but if they are not used, -they won’t have any effect on the contract’s behavior. - -## Usage - -This is how a contract with multiple components looks when using the macro. - -``` -#[with_components(Account, SRC5, SRC9, Upgradeable)] -#[starknet::contract(account)] -mod OutsideExecutionAccountUpgradeable { - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ClassHash, ContractAddress}; - - // External - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - #[abi(embed_v0)] - impl OutsideExecutionV2Impl = - SRC9Component::OutsideExecutionV2Impl; - - #[storage] - struct Storage {} - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - self.src9.initializer(); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.account.assert_only_self(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -This is how the same contract looks using regular syntax. - -``` -#[starknet::contract(account)] -mod OutsideExecutionAccountUpgradeable { - use openzeppelin::account::AccountComponent; - use openzeppelin::account::extensions::SRC9Component; - use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::upgrades::UpgradeableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; - use starknet::ClassHash; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: SRC9Component, storage: src9, event: SRC9Event); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // External - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - #[abi(embed_v0)] - impl OutsideExecutionV2Impl = - SRC9Component::OutsideExecutionV2Impl; - - // Internal - impl AccountInternalImpl = AccountComponent::InternalImpl; - impl OutsideExecutionInternalImpl = SRC9Component::InternalImpl; - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - src9: SRC9Component::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - SRC9Event: SRC9Component::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - self.src9.initializer(); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.account.assert_only_self(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -← Macros - -type\_hash → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/presets - -## Presets - OpenZeppelin Docs - -# Presets - -Presets are ready-to-deploy contracts provided by the library. Since presets are intended to be very simple -and as generic as possible, there’s no support for custom or complex contracts such as `ERC20Pausable` or `ERC721Mintable`. - -| | | -| --- | --- | -| | For contract customization and combination of modules you can use Wizard for Cairo, our code-generation tool. | - -## Available presets - -List of available presets and their corresponding Sierra class hashes. Like Contracts for Cairo, -use of preset contracts are subject to the terms of the -MIT License. - -| | | -| --- | --- | -| | Class hashes were computed using cairo 2.11.4 and the `scarb --release` profile. | - -| | | -| --- | --- | -| | Before version 2.0.0, class hashes were computed using the `scarb --dev` profile. | - -| Name | Sierra Class Hash | -| --- | --- | -| `AccountUpgradeable` | `0x79a9a12fdfa0481e8d8d46599b90226cd7247b2667358bb00636dd864002314` | -| `ERC20Upgradeable` | `0x65daa9c6005dcbccb0571ffdf530e2e263d1ff00eac2cbd66b2d0fa0871dafa` | -| `ERC721Upgradeable` | `0x6d1cd9d8c2008d36bd627e204c3e5f565d4e632de4e50b36d2388c7ba7a64ce` | -| `ERC1155Upgradeable` | `0x36d453774916578336db8f5f18257f0211011270a5c31adf3a2bd86416943b7` | -| `EthAccountUpgradeable` | `0x70177fca30a0a9025465f16f8174d4ea220f61bf44cb1beecb89459fe966285` | -| `UniversalDeployer` | `0x1b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8` | -| `VestingWallet` | `0x10a786d4e5f74d68e0a500aeadbf7a81486f069c06afa242a050a1a09ac42f0` | - -| | | -| --- | --- | -| | starkli class-hash command can be used to compute the class hash from a Sierra artifact. | - -## Usage - -These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. -Simply deploy the preset class hash and add the appropriate constructor arguments. -Deploying the ERC20Upgradeable preset with starkli, for example, will look like this: - -``` -starkli deploy 0x65daa9c6005dcbccb0571ffdf530e2e263d1ff00eac2cbd66b2d0fa0871dafa \ - \ - --network="sepolia" -``` - -If a class hash has yet to be declared, copy/paste the preset contract code and declare it locally. -Start by setting up a project and installing the Contracts for Cairo library. -Copy the target preset contract from the presets directory and paste it in the new project’s `src/lib.cairo` like this: - -``` -// src/lib.cairo - -#[starknet::contract] -mod ERC20Upgradeable { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ContractAddress, ClassHash}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Upgradeable - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress, - owner: ContractAddress - ) { - self.ownable.initializer(owner); - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -Next, compile the contract. - -``` -scarb build -``` - -Finally, declare the preset. - -``` -starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ - --network="sepolia" -``` - -← Components - -Interfaces and Dispatchers → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/security - -## Security - OpenZeppelin Docs - -# Security - -The following documentation provides context, reasoning, and examples of modules found under `openzeppelin_security`. - -| | | -| --- | --- | -| | Expect these modules to evolve. | - -## Initializable - -The Initializable component provides a simple mechanism that mimics -the functionality of a constructor. -More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. - -### Usage - -You can use the component in your contracts like this: - -``` -#[starknet::contract] -mod MyInitializableContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - impl InternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage, - param: felt252 - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } - - fn initializer(ref self: ContractState, some_param: felt252) { - // Makes the method callable only once - self.initializable.initialize(); - - // Initialization logic - self.param.write(some_param); - } -} -``` - -| | | -| --- | --- | -| | This Initializable pattern should only be used in one function. | - -### Interface - -The component provides the following external functions as part of the `InitializableImpl` implementation: - -``` -#[starknet::interface] -pub trait InitializableABI { - fn is_initialized() -> bool; -} -``` - -## Pausable - -The Pausable component allows contracts to implement an emergency stop mechanism. -This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. - -To become pausable, the contract should include `pause` and `unpause` functions (which should be protected). -For methods that should be available only when paused or not, insert calls to `assert_paused` and `assert_not_paused` -respectively. - -### Usage - -For example (using the Ownable component for access control): - -``` -#[starknet::contract] -mod MyPausableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_security::PausableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // Pausable - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - pausable: PausableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - PausableEvent: PausableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[external(v0)] - fn pause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.pause(); - } - - #[external(v0)] - fn unpause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.unpause(); - } - - #[external(v0)] - fn when_not_paused(ref self: ContractState) { - self.pausable.assert_not_paused(); - // Do something - } - - #[external(v0)] - fn when_paused(ref self: ContractState) { - self.pausable.assert_paused(); - // Do something - } -} -``` - -### Interface - -The component provides the following external functions as part of the `PausableImpl` implementation: - -``` -#[starknet::interface] -pub trait PausableABI { - fn is_paused() -> bool; -} -``` - -## Reentrancy Guard - -A reentrancy attack occurs when the caller is able to obtain more resources than allowed by recursively calling a target’s function. - -### Usage - -Since Cairo does not support modifiers like Solidity, the ReentrancyGuard -component exposes two methods `start` and `end` to protect functions against reentrancy attacks. -The protected function must call `start` before the first function statement, and `end` before the return statement, as shown below: - -``` -#[starknet::contract] -mod MyReentrancyContract { - use openzeppelin_security::ReentrancyGuardComponent; - - component!( - path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent - ); - - impl InternalImpl = ReentrancyGuardComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - reentrancy_guard: ReentrancyGuardComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ReentrancyGuardEvent: ReentrancyGuardComponent::Event - } - - #[external(v0)] - fn protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } - - #[external(v0)] - fn another_protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } -} -``` - -| | | -| --- | --- | -| | The guard prevents the execution flow occurring inside `protected_function` to call itself or `another_protected_function`, and vice versa. | - -← Merkle Tree - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/udc - -## Universal Deployer Contract - OpenZeppelin Docs - -# Universal Deployer Contract - -The Universal Deployer Contract (UDC) is a singleton smart contract that wraps the deploy syscall to expose it to any contract that doesn’t implement it, such as account contracts. You can think of it as a standardized generic factory for Starknet contracts. - -Since Starknet has no deployment transaction type, it offers a standardized way to deploy smart contracts by following the Standard Deployer Interface and emitting a ContractDeployed event. - -For details on the motivation and the decision making process, see the Universal Deployer Contract proposal. - -## UDC contract address - -The UDC is deployed at address `0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125` on Starknet sepolia and mainnet. - -## Interface - -``` -#[starknet::interface] -pub trait IUniversalDeployer { - fn deploy_contract( - class_hash: ClassHash, - salt: felt252, - not_from_zero: bool, - calldata: Span - ) -> ContractAddress; -} -``` - -## Deploying a contract with the UDC - -First, declare the target contract (if it’s not already declared). -Next, call the UDC’s `deploy_contract` method. -Here’s an implementation example in Cairo: - -``` -use openzeppelin_utils::interfaces::{IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait}; - -const UDC_ADDRESS: felt252 = 0x04...; - -fn deploy() -> ContractAddress { - let dispatcher = IUniversalDeployerDispatcher { - contract_address: UDC_ADDRESS.try_into().unwrap() - }; - - // Deployment parameters - let class_hash = class_hash_const::< - 0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6 - >(); - let salt = 1234567879; - let not_from_zero = true; - let calldata = array![]; - - // The UDC returns the deployed contract address - dispatcher.deploy_contract(class_hash, salt, not_from_zero, calldata.span()) -} -``` - -## Deployment types - -The Universal Deployer Contract offers two types of addresses to deploy: origin-dependent and origin-independent. -As the names suggest, the origin-dependent type includes the deployer’s address in the address calculation, -whereas, the origin-independent type does not. -The `not_from_zero` boolean parameter ultimately determines the type of deployment. - -| | | -| --- | --- | -| | When deploying a contract that uses `get_caller_address` in the constructor calldata, remember that the UDC, not the account, deploys that contract. Therefore, querying `get_caller_address` in a contract’s constructor returns the UDC’s address, *not the account’s address*. | - -### Origin-dependent - -By making deployments dependent upon the origin address, users can reserve a whole address space to prevent someone else from taking ownership of the address. - -Only the owner of the origin address can deploy to those addresses. - -Achieving this type of deployment necessitates that the origin sets `not_from_zero` to `true` in the deploy\_contract call. -Under the hood, the function passes a modified salt to the `deploy_syscall`, which is the hash of the origin’s address with the given salt. - -To deploy a unique contract address pass: - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, true, calldata.span()); -``` - -### Origin-independent - -Origin-independent contract deployments create contract addresses independent of the deployer and the UDC instance. -Instead, only the class hash, salt, and constructor arguments determine the address. -This type of deployment enables redeployments of accounts and known systems across multiple networks. -To deploy a reproducible deployment, set `not_from_zero` to `false`. - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, false, calldata.span()); -``` - -## Version changes - -| | | -| --- | --- | -| | See the previous Universal Deployer API for the initial spec. | - -The latest iteration of the UDC includes some notable changes to the API which include: - -* `deployContract` method is replaced with the snake\_case deploy\_contract. -* `unique` parameter is replaced with `not_from_zero` in both the `deploy_contract` method and ContractDeployed event. - -## Precomputing contract addresses - -This library offers utility functions written in Cairo to precompute contract addresses. -They include the generic calculate\_contract\_address\_from\_deploy\_syscall as well as the UDC-specific calculate\_contract\_address\_from\_udc. -Check out the deployments for more information. - -← Common - -UDC Appchain Deployment → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/upgrades - -## Upgrades - OpenZeppelin Docs - -# Upgrades - -In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns. - -Starknet has native upgradeability through a syscall that updates the contract source code, removing the need for proxies. - -| | | -| --- | --- | -| | Make sure you follow our security recommendations before upgrading. | - -## Replacing contract classes - -To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class. - -Contract Classes represent the source code of a program. All contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by a class hash, and before a contract of a class can be deployed, the class hash needs to be declared. - -### `replace_class_syscall` - -The `replace_class` syscall allows a contract to update its source code by replacing its class hash once deployed. - -``` -/// Upgrades the contract source code to the new contract class. -fn upgrade(new_class_hash: ClassHash) { - assert(!new_class_hash.is_zero(), 'Class hash cannot be zero'); - starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); -} -``` - -| | | -| --- | --- | -| | If a contract is deployed without this mechanism, its class hash can still be replaced through library calls. | - -## `Upgradeable` component - -OpenZeppelin Contracts for Cairo provides Upgradeable to add upgradeability support to your contracts. - -### Usage - -Upgrades are often very sensitive operations, and some form of access control is usually required to -avoid unauthorized upgrades. The Ownable module is used in this example. - -| | | -| --- | --- | -| | We will be using the following module to implement the IUpgradeable interface described in the API Reference section. | - -``` -#[starknet::contract] -mod UpgradeableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::ClassHash; - use starknet::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; - - #[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 - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[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(); - - // Replace the class hash upgrading the contract - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -## Security - -Upgrades can be very sensitive operations, and security should always be top of mind while performing one. Please make sure you thoroughly review the changes and their consequences before upgrading. Some aspects to consider are: - -* API changes that might affect integration. For example, changing an external function’s arguments might break existing contracts or offchain systems calling your contract. -* Storage changes that might result in lost data (e.g. changing a storage slot name, making existing storage inaccessible). -* Collisions (e.g. mistakenly reusing the same storage slot from another component) are also possible, although less likely if best practices are followed, for example prepending storage variables with the component’s name (e.g. `ERC20_balances`). -* Always check for backwards compatibility before upgrading between versions of OpenZeppelin Contracts. - -## Proxies in Starknet - -Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them. - -In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement. - -Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used -for redirecting every potential function call to the implementation. This means that a generic proxy contract -can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward -their execution to another contract class. -This can still be useful for example to upgrade the logic of some functions. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0/wizard - -## Wizard for Cairo - OpenZeppelin Docs - -# Wizard for Cairo - -Not sure where to start? Use the interactive generator below to bootstrap your -contract and learn about the components offered in OpenZeppelin Contracts for Cairo. - -| | | -| --- | --- | -| | We strongly recommend checking the Components section to understand how to extend from our library. | - -← Overview - -Components → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/ - -## Contracts for Cairo - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Contracts for Cairo - -**A library for secure smart contract development** written in Cairo for Starknet. This library consists of a set of reusable components to build custom smart contracts, as well as -ready-to-deploy presets. You can also find other utilities including interfaces and dispatchers and test utilities -that facilitate testing with Starknet Foundry. - -| | | -| --- | --- | -| | This repo contains highly experimental code. Expect rapid iteration. **Use at your own risk.** | - -| | | -| --- | --- | -| | You can track our roadmap and future milestones in our Github Project. | - -## Installation - -The library is available as a Scarb package. Follow this guide for installing Cairo and Scarb on your machine -before proceeding, and run the following command to check that the installation was successful: - -``` -$ scarb --version - -scarb 2.9.4 (d3be9ebe1 2025-02-19) -cairo: 2.9.4 (https://crates.io/crates/cairo-lang-compiler/2.9.4) -sierra: 1.6.0 -``` - -### Set up your project - -Create an empty directory, and `cd` into it: - -``` -mkdir my_project/ && cd my_project/ -``` - -Initialize a new Scarb project: - -``` -scarb init -``` - -The contents of `my_project/` should now look like this: - -``` -$ ls - -Scarb.toml src -``` - -### Install the library - -Install the library by declaring it as a dependency in the project’s `Scarb.toml` file: - -``` -[dependencies] -openzeppelin = "2.0.0-alpha.1" -``` - -The previous example would import the entire library. We can also add each package as a separate dependency to -improve the building time by not including modules that won’t be used: - -``` -[dependencies] -openzeppelin_access = "2.0.0-alpha.1" -openzeppelin_token = "2.0.0-alpha.1" -``` - -## Basic usage - -This is how it looks to build an ERC20 contract using the ERC20 component. -Copy the code into `src/lib.cairo`. - -``` -#[starknet::contract] -mod MyERC20Token { - // NOTE: If you added the entire library as a dependency, - // use `openzeppelin::token` instead. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -You can now compile it: - -``` -scarb build -``` - -Wizard → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/access - -## Access - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Access - -Access control—​that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. -The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. -It is therefore critical to understand how you implement it, lest someone else -steals your whole system. - -## Ownership and `Ownable` - -The most common and basic form of access control is the concept of ownership: there’s an account that is the `owner` -of a contract and can do administrative tasks on it. -This approach is perfectly reasonable for contracts that have a single administrative user. - -OpenZeppelin Contracts for Cairo provides OwnableComponent for implementing ownership in your contracts. - -### Usage - -Integrating this component into a contract first requires assigning an owner. -The implementing contract’s constructor should set the initial owner by passing the owner’s address to Ownable’s -`initializer` like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Set the initial owner of the contract - self.ownable.initializer(owner); - } - - (...) -} -``` - -To restrict a function’s access to the owner only, add in the `assert_only_owner` method: - -``` -#[starknet::contract] -mod MyContract { - (...) - - #[external(v0)] - fn only_owner_allowed(ref self: ContractState) { - // This function can only be called by the owner - self.ownable.assert_only_owner(); - - (...) - } -} -``` - -### Interface - -This is the full interface of the `OwnableMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableABI { - // IOwnable - fn owner() -> ContractAddress; - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableCamelOnly - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -Ownable also lets you: - -* `transfer_ownership` from the owner account to a new one, and -* `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern - after an initial stage with centralized administration is over. - -| | | -| --- | --- | -| | Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` will no longer be callable! | - -### Two step transfer - -The component also offers a more robust way of transferring ownership via the -OwnableTwoStepImpl implementation. A two step transfer mechanism helps -to prevent unintended and irreversible owner transfers. Simply replace the `OwnableMixinImpl` -with its respective two step variant: - -``` -#[abi(embed_v0)] -impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl; -``` - -#### Interface - -This is the full interface of the two step `OwnableTwoStepMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableTwoStepABI { - // IOwnableTwoStep - fn owner() -> ContractAddress; - fn pending_owner() -> ContractAddress; - fn accept_ownership(); - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableTwoStepCamelOnly - fn pendingOwner() -> ContractAddress; - fn acceptOwnership(); - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -## Role-Based `AccessControl` - -While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of -authorization are often needed. You may want for an account to have permission to ban users from a system, but not -create new tokens. Role-Based Access Control (RBAC) offers -flexibility in this regard. - -In essence, we will be defining multiple roles, each allowed to perform different sets of actions. -An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for -instead of simply using `assert_only_owner`. This check can be enforced through `assert_only_role`. -Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. - -Most software uses access control systems that are role-based: some users are regular users, some may be supervisors -or managers, and a few will often have administrative privileges. - -### Usage - -For each role that you want to define, you will create a new *role identifier* that is used to grant, revoke, and -check if an account has that role. See Creating role identifiers for information -on creating identifiers. - -Here’s a simple example of implementing AccessControl on a portion of an ERC20 token contract which defines -and sets a 'minter' role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::MINTER_ROLE; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } -} -``` - -| | | -| --- | --- | -| | Make sure you fully understand how AccessControl works before using it on your system, or copy-pasting the examples from this guide. | - -While clear and explicit, this isn’t anything we wouldn’t have been able to achieve with -Ownable. Where AccessControl shines the most is in scenarios where granular -permissions are required, which can be implemented by defining *multiple* roles. - -Let’s augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress, - burner: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - self.accesscontrol._grant_role(BURNER_ROLE, burner); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -So clean! -By splitting concerns this way, more granular levels of permission may be implemented than were possible with the -simpler ownership approach to access control. Limiting what each component of a system is able to do is known -as the principle of least privilege, and is a good -security practice. Note that each account may still have more than one role, if so desired. - -### Granting and revoking roles - -The ERC20 token example above uses `_grant_role`, -an `internal` function that is useful when programmatically assigning -roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? - -By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making -the `assert_only_role` check pass. To grant and revoke roles dynamically, you will need help from the role’s *admin*. - -Every role has an associated admin role, which grants permission to call the -`grant_role` and -`revoke_role` functions. -A role can be granted or revoked by using these if the calling account has the corresponding admin role. -Multiple roles may have the same admin role to make management easier. -A role’s admin can even be the same role itself, which would cause accounts with that role to be able -to also grant and revoke it. - -This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also -provides an easy way to manage simpler applications. `AccessControl` includes a special role with the role identifier -of `0`, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. -An account with this role will be able to manage any other role, unless -`set_role_admin` is used to select a new admin role. - -Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - admin: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -| | | -| --- | --- | -| | The `grant_role` and `revoke_role` functions are automatically exposed as `external` functions from the `AccessControlImpl` by leveraging the `#[abi(embed_v0)]` annotation. | - -Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. -However, because those roles' admin role is the default admin role, and that role was granted to the 'admin', that -same account can call `grant_role` to give minting or burning permission, and `revoke_role` to remove it. - -Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary -over time. It can also be used to support use cases such as KYC, -where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -### Creating role identifiers - -In the Solidity implementation of AccessControl, contracts generally refer to the -keccak256 hash -of a role as the role identifier. - -For example: - -``` -bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") -``` - -These identifiers take up 32 bytes (256 bits). - -Cairo field elements (`felt252`) store a maximum of 252 bits. -With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. -Some ideas to consider: - -* Use sn\_keccak instead. -* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the - Cairo corelib. - -| | | -| --- | --- | -| | The `selector!` macro can be used to compute sn\_keccak in Cairo. | - -### Interface - -This is the full interface of the `AccessControlMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccessControlABI { - // IAccessControl - fn has_role(role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(role: felt252) -> felt252; - fn grant_role(role: felt252, account: ContractAddress); - fn revoke_role(role: felt252, account: ContractAddress); - fn renounce_role(role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(role: felt252) -> felt252; - fn grantRole(role: felt252, account: ContractAddress); - fn revokeRole(role: felt252, account: ContractAddress); - fn renounceRole(role: felt252, account: ContractAddress); - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -`AccessControl` also lets you `renounce_role` from the calling account. -The method expects an account as input as an extra security measure, to ensure you are -not renouncing a role from an unintended account. - -← SNIP12 and Typed Messages - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/accounts - -## Accounts - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Accounts - -Unlike Ethereum where accounts are derived from a private key, all Starknet accounts are contracts. This means there’s no Externally Owned Account (EOA) -concept on Starknet. - -Instead, the network features native account abstraction and signature validation happens at the contract level. - -For a general overview of account abstraction, see -Starknet’s documentation. -A more detailed discussion on the topic can be found in -Starknet Shaman’s forum. - -| | | -| --- | --- | -| | For detailed information on the usage and implementation check the API Reference section. | - -## What is an account? - -Accounts in Starknet are smart contracts, and so they can be deployed and interacted -with like any other contract, and can be extended to implement any custom logic. However, an account is a special type -of contract that is used to validate and execute transactions. For this reason, it must implement a set of entrypoints -that the protocol uses for this execution flow. The SNIP-6 proposal defines a standard interface for accounts, -supporting this execution flow and interoperability with DApps in the ecosystem. - -### ISRC6 Interface - -``` -/// Represents a call to a target contract function. -struct Call { - to: ContractAddress, - selector: felt252, - calldata: Span -} - -/// Standard Account Interface -#[starknet::interface] -pub trait ISRC6 { - /// Executes a transaction through the account. - fn __execute__(calls: Array); - - /// Asserts whether the transaction is valid to be executed. - fn __validate__(calls: Array) -> felt252; - - /// Asserts whether a given signature for a given hash is valid. - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; -} -``` - -| | | -| --- | --- | -| | The `calldata` member of the `Call` struct in the accounts has been updated to `Span` for optimization purposes, but the interface ID remains the same for backwards compatibility. This inconsistency will be fixed in future releases. | - -SNIP-6 adds the `is_valid_signature` method. This method is not used by the protocol, but it’s useful for -DApps to verify the validity of signatures, supporting features like Sign In with Starknet. - -SNIP-6 also defines that compliant accounts must implement the SRC5 interface following SNIP-5, as -a mechanism for detecting whether a contract is an account or not through introspection. - -### ISRC5 Interface - -``` -/// Standard Interface Detection -#[starknet::interface] -pub trait ISRC5 { - /// Queries if a contract implements a given interface. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -SNIP-6 compliant accounts must return `true` when queried for the ISRC6 interface ID. - -Even though these interfaces are not enforced by the protocol, it’s recommended to implement them for enabling -interoperability with the ecosystem. - -### Protocol-level methods - -The Starknet protocol uses a few entrypoints for abstracting the accounts. We already mentioned the first two -as part of the ISRC6 interface, and both are required for enabling accounts to be used for executing transactions. The rest are optional: - -1. `__validate__` verifies the validity of the transaction to be executed. This is usually used to validate signatures, - but the entrypoint implementation can be customized to feature any validation mechanism with some limitations. -2. `__execute__` executes the transaction if the validation is successful. -3. `__validate_declare__` optional entrypoint similar to `__validate__` but for transactions - meant to declare other contracts. -4. `__validate_deploy__` optional entrypoint similar to `__validate__` but meant for counterfactual deployments. - -| | | -| --- | --- | -| | Although these entrypoints are available to the protocol for its regular transaction flow, they can also be called like any other method. | - -## Starknet Account - -Starknet native account abstraction pattern allows for the creation of custom accounts with different validation schemes, but -usually most account implementations validate transactions using the Stark curve which is the most efficient way -of validating signatures since it is a STARK-friendly curve. - -OpenZeppelin Contracts for Cairo provides AccountComponent for implementing this validation scheme. - -### Usage - -Constructing an account contract requires integrating both AccountComponent and SRC5Component. The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `AccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; - - // IPublicKey - fn get_public_key() -> felt252; - fn set_public_key(new_public_key: felt252, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IPublicKeyCamel - fn getPublicKey() -> felt252; - fn setPublicKey(newPublicKey: felt252, signature: Span); -} -``` - -## Ethereum Account - -Besides the Stark-curve account, OpenZeppelin Contracts for Cairo also offers Ethereum-flavored accounts that use the secp256k1 curve for signature validation. -For this the EthAccountComponent must be used. - -### Usage - -Constructing a secp256k1 account contract also requires integrating both EthAccountComponent and SRC5Component. -The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. -Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ClassHash; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `EthAccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait EthAccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IEthDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey - ) -> felt252; - - // IEthPublicKey - fn get_public_key() -> EthPublicKey; - fn set_public_key(new_public_key: EthPublicKey, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IEthPublicKeyCamel - fn getPublicKey() -> EthPublicKey; - fn setPublicKey(newPublicKey: EthPublicKey, signature: Span); -} -``` - -## Deploying an account - -In Starknet there are two ways of deploying smart contracts: using the `deploy_syscall` and doing -counterfactual deployments. -The former can be easily done with the Universal Deployer Contract (UDC), a contract that -wraps and exposes the `deploy_syscall` to provide arbitrary deployments through regular contract calls. -But if you don’t have an account to invoke it, you will probably want to use the latter. - -To do counterfactual deployments, you need to implement another protocol-level entrypoint named -`__validate_deploy__`. Check the counterfactual deployments guide to learn how. - -## Sending transactions - -Let’s now explore how to send transactions through these accounts. - -### Starknet Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -To deploy the account variant, compile the contract and declare the class hash because custom accounts are likely not declared. -This means that you’ll need an account already deployed. - -Next, create the account JSON with Starknet Foundry’s custom account setup and include the `--class-hash` flag with the declared class hash. -The flag enables custom account variants. - -| | | -| --- | --- | -| | The following examples use `sncast` v0.23.0. | - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account create \ - --name my-custom-account \ - --class-hash 0x123456... -``` - -This command will output the precomputed contract address and the recommended `max-fee`. -To counterfactually deploy the account, send funds to the address and then deploy the custom account. - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account deploy \ - --name my-custom-account -``` - -Once the account is deployed, set the `--account` flag with the custom account name to send transactions from that account. - -``` -$ sncast \ - --account my-custom-account \ - --url http://127.0.0.1:5050 \ - invoke \ - --contract-address 0x123... \ - --function "some_function" \ - --calldata 1 2 3 -``` - -### Ethereum Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -Special tooling is required in order to deploy and send transactions with an Ethereum-flavored account contract. -The following examples utilize the StarknetJS library. - -Compile and declare the contract on the target network. -Next, precompute the EthAccount contract address using the declared class hash. - -| | | -| --- | --- | -| | The following examples use unreleased features from StarknetJS (`starknetjs@next`) at commit d002baea0abc1de3ac6e87a671f3dec3757437b3. | - -``` -import * as dotenv from 'dotenv'; -import { CallData, EthSigner, hash } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Calculate EthAccount address -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountClassHash = ''; -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployerAddress = '0x0'; -const ethContractAddress = hash.calculateContractAddressFromHash( - salt, - ethAccountClassHash, - ethAccountConstructorCalldata, - deployerAddress -); -console.log('Pre-calculated EthAccount address: ', ethContractAddress); -``` - -Send funds to the pre-calculated EthAccount address and deploy the contract. - -``` -import * as dotenv from 'dotenv'; -import { Account, CallData, EthSigner, RpcProvider, stark } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare payload -const ethAccountClassHash = '' -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployPayload = { - classHash: ethAccountClassHash, - constructorCalldata: ethAccountConstructorCalldata, - addressSalt: salt, -}; - -// Deploy -const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); -const { transaction_hash, contract_address } = await ethAccount.deployAccount( - deployPayload, - { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } -); -await provider.waitForTransaction(transaction_hash); -console.log('EthAccount deployed at: ', contract_address); -``` - -Once deployed, connect the EthAccount instance to the target contract which enables calls to come from the EthAccount. -Here’s what an ERC20 transfer from an EthAccount looks like. - -``` -import * as dotenv from 'dotenv'; -import { Account, RpcProvider, Contract, EthSigner } from 'starknet'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare target contract -const erc20 = new Contract(compiledErc20.abi, erc20Address, provider); - -// Connect EthAccount with the target contract -erc20.connect(ethAccount); - -// Execute ERC20 transfer -const transferCall = erc20.populate('transfer', { - recipient: recipient.address, - amount: 50n -}); -const tx = await erc20.transfer( - transferCall.calldata, { maxFee: 900_000_000_000_000 } -); -await provider.waitForTransaction(tx.transaction_hash); -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/backwards-compatibility - -## Backwards Compatibility - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Backwards Compatibility - -OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees. - -Bear in mind that while releasing versions, we treat minors as majors and patches as minors, in accordance with semantic versioning. This means that `v2.1.0` could be adding features to `v2.0.0`, while `v3.0.0` would be considered a breaking release. - -## API - -In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below. - -### Security - -Infrequently, a patch or minor update will remove or change an API in a breaking way but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory. - -### Errors - -The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified. - -### Major releases - -Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem. - -An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another. - -In the case of breaking "upgrade compatibility", an entry to the changelog will be added listing those breaking changes. - -## Storage layout - -Patch updates will always preserve storage layout compatibility, and after `v2.0.0-alpha.1` minors will too. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent. - -## Cairo version - -The minimum Cairo version required to compile the contracts will remain unchanged for patch updates, but it may change for minors. - -← Test Utilities - -Contracts for Solidity → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/components - -## Components - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Components - -The following documentation provides reasoning and examples on how to use Contracts for Cairo components. - -Starknet components are separate modules that contain storage, events, and implementations that can be integrated into a contract. -Components themselves cannot be declared or deployed. -Another way to think of components is that they are abstract modules that must be instantiated. - -| | | -| --- | --- | -| | For more information on the construction and design of Starknet components, see the Starknet Shamans post and the Cairo book. | - -## Building a contract - -### Setup - -The contract should first import the component and declare it with the `component!` macro: - -``` -#[starknet::contract] -mod MyContract { - // Import the component - use openzeppelin_security::InitializableComponent; - - // Declare the component - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); -} -``` - -The `path` argument should be the imported component itself (in this case, InitializableComponent). -The `storage` and `event` arguments are the variable names that will be set in the `Storage` struct and `Event` enum, respectively. -Note that even if the component doesn’t define any events, the compiler will still create an empty event enum inside the component module. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -The `#[substorage(v0)]` attribute must be included for each component in the `Storage` trait. -This allows the contract to have indirect access to the component’s storage. -See Accessing component storage for more on this. - -The `#[flat]` attribute for events in the `Event` enum, however, is not required. -For component events, the first key in the event log is the component ID. -Flattening the component event removes it, leaving the event ID as the first key. - -### Implementations - -Components come with granular implementations of different interfaces. -This allows contracts to integrate only the implementations that they’ll use and avoid unnecessary bloat. -Integrating an implementation looks like this: - -``` -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - // Gives the contract access to the implementation methods - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -Defining an `impl` gives the contract access to the methods within the implementation from the component. -For example, `is_initialized` is defined in the `InitializableImpl`. -A function on the contract level can expose it like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - impl InitializableImpl = - InitializableComponent::InitializableImpl; - - #[external(v0)] - fn is_initialized(ref self: ContractState) -> bool { - self.initializable.is_initialized() - } -} -``` - -While there’s nothing wrong with manually exposing methods like in the previous example, this process can be tedious for implementations with many methods. -Fortunately, a contract can embed implementations which will expose all of the methods of the implementation. -To embed an implementation, add the `#[abi(embed_v0)]` attribute above the `impl`: - -``` -#[starknet::contract] -mod MyContract { - (...) - - // This attribute exposes the methods of the `impl` - #[abi(embed_v0)] - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -`InitializableImpl` defines the `is_initialized` method in the component. -By adding the embed attribute, `is_initialized` becomes a contract entrypoint for `MyContract`. - -| | | -| --- | --- | -| | Embeddable implementations, when available in this library’s components, are segregated from the internal component implementation which makes it easier to safely expose. Components also separate granular implementations from mixin implementations. The API documentation design reflects these groupings. See ERC20Component as an example which includes: * **Embeddable Mixin Implementation** * **Embeddable Implementations** * **Internal Implementations** * **Events** | - -### Mixins - -Mixins are impls made of a combination of smaller, more specific impls. -While separating components into granular implementations offers flexibility, -integrating components with many implementations can appear crowded especially if the contract uses all of them. -Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. - -Compare the following code blocks to see the benefit of using a mixin when creating an account contract. - -#### Account without mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl SRC6Impl = AccountComponent::SRC6Impl; -#[abi(embed_v0)] -impl DeclarerImpl = AccountComponent::DeclarerImpl; -#[abi(embed_v0)] -impl DeployableImpl = AccountComponent::DeployableImpl; -#[abi(embed_v0)] -impl PublicKeyImpl = AccountComponent::PublicKeyImpl; -#[abi(embed_v0)] -impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; -#[abi(embed_v0)] -impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; - -#[abi(embed_v0)] -impl SRC5Impl = SRC5Component::SRC5Impl; -``` - -#### Account with mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl AccountMixinImpl = AccountComponent::AccountMixinImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; -``` - -The rest of the setup for the contract, however, does not change. -This means that component dependencies must still be included in the `Storage` struct and `Event` enum. -Here’s a full example of an account contract that embeds the `AccountMixinImpl`: - -``` -#[starknet::contract] -mod Account { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // This embeds all of the methods from the many AccountComponent implementations - // and also includes `supports_interface` from `SRC5Impl` - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Initializers - -| | | -| --- | --- | -| | Failing to use a component’s `initializer` can result in irreparable contract deployments. Always read the API documentation for each integrated component. | - -Some components require some sort of setup upon construction. -Usually, this would be a job for a constructor; however, components themselves cannot implement constructors. -Components instead offer `initializer`s within their `InternalImpl` to call from the contract’s constructor. -Let’s look at how a contract would integrate OwnableComponent: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Invoke ownable's `initializer` - self.ownable.initializer(owner); - } -} -``` - -### Immutable Config - -While initializers help set up the component’s initial state, some require configuration that may be defined -as constants, saving gas by avoiding the necessity of reading from storage each time the variable needs to be used. The -Immutable Component Config pattern helps with this matter by allowing the implementing contract to define a set of -constants declared in the component, customizing its functionality. - -| | | -| --- | --- | -| | The Immutable Component Config standard is defined in the SRC-107. | - -Here’s an example of how to use the Immutable Component Config pattern with the ERC2981Component: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::common::erc2981::ERC2981Component; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc2981: ERC2981Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC2981Event: ERC2981Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - // Define the immutable config - pub impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig { - const FEE_DENOMINATOR: u128 = 10_000; - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### Default config - -Sometimes, components implementing the Immutable Component Config pattern provide a default configuration that can be -directly used without implementing the `ImmutableConfig` trait locally. When provided, this implementation will be named -`DefaultConfig` and will be available in the same module containing the component, as a sibling. - -In the following example, the `DefaultConfig` trait is used to define the `FEE_DENOMINATOR` config constant. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - // Bring the DefaultConfig trait into scope - use openzeppelin_token::common::erc2981::{ERC2981Component, DefaultConfig}; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - (...) - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - (...) - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### `validate` function - -The `ImmutableConfig` trait may also include a `validate` function with a default implementation, which -asserts that the configuration is correct, and must not be overridden by the implementing contract. For more information -on how to use this function, refer to the validate section of the SRC-107. - -### Dependencies - -Some components include dependencies of other components. -Contracts that integrate components with dependencies must also include the component dependency. -For instance, AccessControlComponent depends on SRC5Component. -Creating a contract with `AccessControlComponent` should look like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - #[abi(embed_v0)] - impl AccessControlCamelImpl = - AccessControlComponent::AccessControlCamelImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - (...) -} -``` - -## Customization - -| | | -| --- | --- | -| | Customizing implementations and accessing component storage can potentially corrupt the state, bypass security checks, and undermine the component logic. **Exercise extreme caution**. See Security. | - -### Hooks - -Hooks are entrypoints to the business logic of a token component that are accessible at the contract level. -This allows contracts to insert additional behaviors before and/or after token transfers (including mints and burns). -Prior to hooks, extending functionality required contracts to create custom implementations. - -All token components include a generic hooks trait that include empty default functions. -When creating a token contract, the using contract must create an implementation of the hooks trait. -Suppose an ERC20 contract wanted to include Pausable functionality on token transfers. -The following snippet leverages the `before_update` hook to include this behavior. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_security::pausable::PausableComponent::InternalTrait; - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // Create the hooks implementation - impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { - // Occurs before token transfers - fn before_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - // Access local state from component state - let contract_state = self.get_contract(); - // Call function from integrated component - contract_state.pausable.assert_not_paused(); - } - - // Omitting the `after_update` hook because the default behavior - // is already implemented in the trait - } - - (...) -} -``` - -Notice that the `self` parameter expects a component state type. -Instead of passing the component state, the using contract’s state can be passed which simplifies the syntax. -The hook then moves the scope up with the Cairo-generated `get_contract` through the `HasComponent` trait (as illustrated with ERC20Component in this example). -From here, the hook can access the using contract’s integrated components, storage, and implementations. - -Be advised that even if a token contract does not require hooks, the hooks trait must still be implemented. -The using contract may instantiate an empty impl of the trait; -however, the Contracts for Cairo library already provides the instantiated impl to abstract this away from contracts. -The using contract just needs to bring the implementation into scope like this: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_token::erc20::ERC20HooksEmptyImpl; - - (...) -} -``` - -| | | -| --- | --- | -| | For a more in-depth guide on hooks, see Extending Cairo Contracts with Hooks. | - -### Custom implementations - -There are instances where a contract requires different or amended behaviors from a component implementation. -In these scenarios, a contract must create a custom implementation of the interface. -Let’s break down a pausable ERC20 contract to see what that looks like. -Here’s the setup: - -``` -#[starknet::contract] -mod ERC20Pausable { - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - // Import the ERC20 interfaces to create custom implementations - use openzeppelin_token::erc20::interface::{IERC20, IERC20CamelOnly}; - use starknet::ContractAddress; - - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // `ERC20MetadataImpl` can keep the embed directive because the implementation - // will not change - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - // Do not add the embed directive to these implementations because - // these will be customized - impl ERC20Impl = ERC20Component::ERC20Impl; - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -The first thing to notice is that the contract imports the interfaces of the implementations that will be customized. -These will be used in the next code example. - -Next, the contract includes the ERC20Component implementations; however, `ERC20Impl` and `ERC20CamelOnlyImplt` are **not** embedded. -Instead, we want to expose our custom implementation of an interface. -The following example shows the pausable logic integrated into the ERC20 implementations: - -``` -#[starknet::contract] -mod ERC20Pausable { - (...) - - // Custom ERC20 implementation - #[abi(embed_v0)] - impl CustomERC20Impl of IERC20 { - fn transfer( - ref self: ContractState, recipient: ContractAddress, amount: u256 - ) -> bool { - // Add the custom logic - self.pausable.assert_not_paused(); - // Add the original implementation method from `IERC20Impl` - self.erc20.transfer(recipient, amount) - } - - fn total_supply(self: @ContractState) -> u256 { - // This method's behavior does not change from the component - // implementation, but this method must still be defined. - // Simply add the original implementation method from `IERC20Impl` - self.erc20.total_supply() - } - - (...) - } - - // Custom ERC20CamelOnly implementation - #[abi(embed_v0)] - impl CustomERC20CamelOnlyImpl of IERC20CamelOnly { - fn totalSupply(self: @ContractState) -> u256 { - self.erc20.total_supply() - } - - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - self.erc20.balance_of(account) - } - - fn transferFrom( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - self.pausable.assert_not_paused(); - self.erc20.transfer_from(sender, recipient, amount) - } - } -} -``` - -Notice that in the `CustomERC20Impl`, the `transfer` method integrates `pausable.assert_not_paused` as well as `erc20.transfer` from `PausableImpl` and `ERC20Impl` respectively. -This is why the contract defined the `ERC20Impl` from the component in the previous example. - -Creating a custom implementation of an interface must define **all** methods from that interface. -This is true even if the behavior of a method does not change from the component implementation (as `total_supply` exemplifies in this example). - -### Accessing component storage - -There may be cases where the contract must read or write to an integrated component’s storage. -To do so, use the same syntax as calling an implementation method except replace the name of the method with the storage variable like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - (...) - - fn write_to_comp_storage(ref self: ContractState) { - self.initializable.Initializable_initialized.write(true); - } - - fn read_from_comp_storage(self: @ContractState) -> bool { - self.initializable.Initializable_initialized.read() - } -} -``` - -## Security - -The maintainers of OpenZeppelin Contracts for Cairo are mainly concerned with the correctness and security of the code as published in the library. - -Customizing implementations and manipulating the component state may break some important assumptions and introduce vulnerabilities. -While we try to ensure the components remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. -Any and all customizations to the component logic should be carefully reviewed and checked against the source code of the component they are customizing so as to fully understand their impact and guarantee their security. - -← Wizard - -Presets → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/erc1155 - -## ERC1155 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC1155 - -The ERC1155 multi token standard is a specification for fungibility-agnostic token contracts. -The ERC1155 library implements an approximation of EIP-1155 in Cairo for StarkNet. - -## Multi Token Standard - -The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This -is why its balance\_of function differs from ERC20’s and ERC777’s: it has an additional ID argument for the -identifier of the token that you want to query the balance of. - -This is similar to how ERC721 does things, but in that standard a token ID has no concept of balance: each token is -non-fungible and exists or doesn’t. The ERC721 balance\_of function refers to how many different tokens an account -has, not how many of each. On the other hand, in ERC1155 accounts have a distinct balance for each token ID, and -non-fungible tokens are implemented by simply minting a single one of them. - -This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new -contract for each token type, a single ERC1155 token contract can hold the entire system state, reducing deployment -costs and complexity. - -## Usage - -Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s URI and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyERC1155 { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155 Mixin - #[abi(embed_v0)] - impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; - impl ERC1155InternalImpl = ERC1155Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155: ERC1155Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155Event: ERC1155Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - token_uri: ByteArray, - recipient: ContractAddress, - token_ids: Span, - values: Span - ) { - self.erc1155.initializer(token_uri); - self - .erc1155 - .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC1155Component. -The interface includes the IERC1155 standard interface and the optional IERC1155MetadataURI interface together with ISRC5. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC1155ABI { - // IERC1155 - fn balance_of(account: ContractAddress, token_id: u256) -> u256; - fn balance_of_batch( - accounts: Span, token_ids: Span - ) -> Span; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - value: u256, - data: Span - ); - fn safe_batch_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ); - fn is_approved_for_all( - owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn set_approval_for_all(operator: ContractAddress, approved: bool); - - // IERC1155MetadataURI - fn uri(token_id: u256) -> ByteArray; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IERC1155Camel - fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; - fn balanceOfBatch( - accounts: Span, tokenIds: Span - ) -> Span; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - value: u256, - data: Span - ); - fn safeBatchTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenIds: Span, - values: Span, - data: Span - ); - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - fn setApprovalForAll(operator: ContractAddress, approved: bool); -} -``` - -## ERC1155 Compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: - -* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. -* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. -* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. - -## Batch operations - -Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, balance\_of\_batch and safe\_batch\_transfer\_from, that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have safe\_transfer\_from for non-batch operations. - -In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as -batch\_mint\_with\_acceptance\_check. - -| | | -| --- | --- | -| | While safe\_transfer\_from and safe\_batch\_transfer\_from prevent loss by checking the receiver can handle the tokens, this yields execution to the receiver which can result in a reentrant call. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC1155 tokens, said contract must implement the `IERC1155Receiver` interface. -The recipient contract must also implement the SRC5 interface which supports interface introspection. - -### IERC1155Receiver - -``` -#[starknet::interface] -pub trait IERC1155Receiver { - fn on_erc1155_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - value: u256, - data: Span - ) -> felt252; - fn on_erc1155_batch_received( - operator: ContractAddress, - from: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC1155Receiver` interface exposes the on\_erc1155\_received and on\_erc1155\_batch\_received methods. -When safe\_transfer\_from and safe\_batch\_transfer\_from are called, they invoke the recipient contract’s `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which **must** return the IERC1155Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo ERC1155ReceiverComponent already returns the correct interface ID for safe token transfers. -To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::ERC1155ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155Receiver Mixin - #[abi(embed_v0)] - impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl; - impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155_receiver: ERC1155ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc1155_receiver.initializer(); - } -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/erc20 - -## ERC20 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC20 - -The ERC20 token standard is a specification for fungible tokens, a type of token where all the units are exactly equal to each other. -`token::erc20::ERC20Component` provides an approximation of EIP-20 in Cairo for Starknet. - -| | | -| --- | --- | -| | Prior to Contracts v0.7.0, ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`. If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract **must** use a custom `decimals` implementation. See the Customizing decimals guide. | - -## Usage - -Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and instantiating the token implementation. -Here’s what that looks like: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } -} -``` - -`MyToken` integrates both the `ERC20Impl` and `ERC20MetadataImpl` with the embed directive which marks the implementations as external in the contract. -While the `ERC20MetadataImpl` is optional, it’s generally recommended to include it because the vast majority of ERC20 tokens provide the metadata methods. -The above example also includes the `ERC20InternalImpl` instance. -This allows the contract’s constructor to initialize the contract and create an initial supply of tokens. - -| | | -| --- | --- | -| | For a more complete guide on ERC20 token mechanisms, see Creating ERC20 Supply. | - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC20Component. -The interface includes the IERC20 standard interface as well as the optional IERC20Metadata. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes an implementation of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20Camel - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -## ERC20 compatibility - -Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. -Some notable differences, however, can still be found, such as: - -* The `ByteArray` type is used to represent strings in Cairo. -* The component offers a dual interface which supports both snake\_case and camelCase methods, as opposed to just camelCase in Solidity. -* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. -* Function selectors are calculated differently between Cairo and Solidity. - -## Customizing decimals - -Cairo, like Solidity, does not support floating-point numbers. -To get around this limitation, ERC20 token contracts may offer a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed. -For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`. -An outside interface would display the token supply as `1.234`. -In the actual contract, however, the supply would still be the integer `1234`. -In other words, **the decimals field in no way changes the actual arithmetic** because all operations are still performed on integers. - -Most contracts use `18` decimals and this was even proposed to be compulsory (see the EIP discussion). - -### The static approach (SRC-107) - -The Contracts for Cairo `ERC20` component leverages SRC-107 to allow for a static and configurable number of decimals. -To use the default `18` decimals, you can use the `DefaultConfig` implementation by just importing it: - -``` -#[starknet::contract] -mod MyToken { - // Importing the DefaultConfig implementation would make decimals 18 by default. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -To customize this value, you can implement the ImmutableConfig trait locally in the contract. -The following example shows how to set the decimals to `6`: - -``` -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - // Custom implementation of the ERC20Component ImmutableConfig. - impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig { - const DECIMALS: u8 = 6; - } -} -``` - -### The storage approach - -For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate. - -| | | -| --- | --- | -| | Note that we are not using the MixinImpl or the DefaultConfig in this case, since we need to customize the IERC20Metadata implementation. | - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::interface; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - // The decimals value is stored locally - decimals: u8, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress, - ) { - // Call the internal function that writes decimals to storage - self._set_decimals(decimals); - - // Initialize ERC20 - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } - - #[abi(embed_v0)] - impl ERC20CustomMetadataImpl of interface::IERC20Metadata { - fn name(self: @ContractState) -> ByteArray { - self.erc20.ERC20_name.read() - } - - fn symbol(self: @ContractState) -> ByteArray { - self.erc20.ERC20_symbol.read() - } - - fn decimals(self: @ContractState) -> u8 { - self.decimals.read() - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn _set_decimals(ref self: ContractState, decimals: u8) { - self.decimals.write(decimals); - } - } -} -``` - -This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage. -Note that the `decimals` state variable must be defined in the contract’s storage because this variable does not exist in the component offered by OpenZeppelin Contracts for Cairo. -It’s important to include a custom ERC20 metadata implementation and NOT use the Contracts for Cairo `ERC20MetadataImpl` in this specific case since the `decimals` method will always return `18`. - -← API Reference - -Creating Supply → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/erc4626 - -## ERC4626 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC4626 - -ERC4626 is an extension of ERC20 that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault. - -We provide a base component of ERC4626 which is designed to allow developers to easily re-configure the vault’s behavior, using traits and hooks, while staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. - -## Security concern: Inflation attack - -### Visualizing the vault - -In exchange for the assets deposited into an ERC4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault. - -* If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets. -* If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets. - -In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line. - -When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets. - -In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. - -### The attack - -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit less than 1 share worth of tokens, you will receive 0 shares, effectively making a donation. - -For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. - -In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%. - -Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens. - -We can clearly see that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. - -The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe. - -Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited. - -An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. - -In math that gives: - -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(0\) | \(0\) | - | -| after attacker’s deposit | \(a\_0\) | \(a\_0\) | \(1\) | -| after attacker’s donation | \(a\_0+a\_1\) | \(a\_0\) | \(\frac{a\_0}{a\_0+a\_1}\) | - -This means a deposit of \(u\) will give \(\frac{u \times a\_0}{a\_0 + a\_1}\) shares. - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[\frac{u \times a\_0}{a\_0+a\_1} < 1 \iff u < 1 + \frac{a\_1}{a\_0}\] - -Using \(a\_0 = 1\) and \(a\_1 = u\) is enough. So the attacker only needs \(u+1\) assets to perform a successful attack. - -It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user’s deposit. In order to target \(\frac{u}{n}\), the user needs to suffer rounding of a similar fraction, which means the user must receive at most \(n\) shares. This results in: - -\[\frac{u \times a\_0}{a\_0+a\_1} < n \iff \frac{u}{n} < 1 + \frac{a\_1}{a\_0}\] - -In this scenario, the attack is \(n\) times less powerful (in how much it is stealing) and costs \(n\) times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings. - -### Defending with a virtual offset - -The defense we propose is based on the approach used in YieldBox. It consists of two parts: - -* Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets. -* Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty. - -These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable to perform an attack. - -Following the previous math definitions, we have: - -* \(\delta\) the vault offset -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(1\) | \(10^\delta\) | \(10^\delta\) | -| after attacker’s deposit | \(1+a\_0\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta\) | -| after attacker’s donation | \(1+a\_0+a\_1\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta \times \frac{1+a\_0}{1+a\_0+a\_1}\) | - -One important thing to note is that the attacker only owns a fraction \(\frac{a\_0}{1 + a\_0}\) of the shares, so when doing the donation, he will only be able to recover that fraction \(\frac{a\_1 \times a\_0}{1 + a\_0}\) of the donation. The remaining \(\frac{a\_1}{1+a\_0}\) are captured by the vault. - -\[\mathit{loss} = \frac{a\_1}{1+a\_0}\] - -When the user deposits \(u\), he receives - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1}\] - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1} < 1\] - -\[\iff 10^\delta \times u < \frac{1+a\_0+a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u < 1 + \frac{a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u \le \mathit{loss}\] - -* If the offset is 0, the attacker loss is at least equal to the user’s deposit. -* If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user. - -This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful. - -The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively. - -\(\delta = 3\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -\(\delta = 3\), \(a\_0 = 100\), \(a\_1 = 10^5\) - -\(\delta = 6\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -## Usage - -### Custom behavior: Adding fees to the vault - -In ERC4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. -In both cases, it is essential to remain compliant with the ERC4626 requirements in regard to the preview functions. - -For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `preview_deposit(100)`. -Similarly, `preview_mint` should account for the fees that the user will have to pay on top of share’s cost. - -As for the `Deposit` event, while this is less clear in the EIP spec itself, -there seems to be consensus that it should include the number of assets paid for by the user, including the fees. - -On the other hand, when withdrawing assets, the number given by the user should correspond to what the user receives. -Any fees should be added to the quote (in shares) performed by `preview_withdraw`. - -The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted). - -The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. -The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault. - -The following example describes how fees proportional to the deposited/withdrawn amount can be implemented: - -``` -/// The mock contract charges fees in terms of assets, not shares. -/// This means that the fees are calculated based on the amount of assets that are being deposited -/// or withdrawn, and not based on the amount of shares that are being minted or redeemed. -/// This is an opinionated design decision for the purpose of testing. -/// DO NOT USE IN PRODUCTION -#[starknet::contract] -pub mod ERC4626Fees { - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::FeeConfigTrait; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::InternalTrait as ERC4626InternalTrait; - use openzeppelin_token::erc20::extensions::erc4626::{DefaultConfig, ERC4626DefaultLimits}; - use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::math; - use openzeppelin_utils::math::Rounding; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: ERC4626Component, storage: erc4626, event: ERC4626Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC4626 - #[abi(embed_v0)] - impl ERC4626ComponentImpl = ERC4626Component::ERC4626Impl; - // ERC4626MetadataImpl is a custom impl of IERC20Metadata - #[abi(embed_v0)] - impl ERC4626MetadataImpl = ERC4626Component::ERC4626MetadataImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC4626InternalImpl = ERC4626Component::InternalImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc4626: ERC4626Component::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - pub entry_fee_basis_point_value: u256, - pub entry_fee_recipient: ContractAddress, - pub exit_fee_basis_point_value: u256, - pub exit_fee_recipient: ContractAddress, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC4626Event: ERC4626Component::Event, - #[flat] - ERC20Event: ERC20Component::Event, - } - - const _BASIS_POINT_SCALE: u256 = 10_000; - - /// Hooks - impl ERC4626HooksEmptyImpl of ERC4626Component::ERC4626HooksTrait { - fn after_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let entry_basis_points = contract_state.entry_fee_basis_point_value.read(); - let fee = contract_state.fee_on_total(assets, entry_basis_points); - let recipient = contract_state.entry_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - - fn before_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let exit_basis_points = contract_state.exit_fee_basis_point_value.read(); - let fee = contract_state.fee_on_raw(assets, exit_basis_points); - let recipient = contract_state.exit_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - } - - /// Adjust fees - impl AdjustFeesImpl of FeeConfigTrait { - fn adjust_deposit( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_deposit(assets) - } - - fn adjust_mint( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_mint(assets) - } - - fn adjust_withdraw( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_withdraw(assets) - } - - fn adjust_redeem( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_redeem(assets) - } - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - underlying_asset: ContractAddress, - initial_supply: u256, - recipient: ContractAddress, - entry_fee: u256, - entry_treasury: ContractAddress, - exit_fee: u256, - exit_treasury: ContractAddress, - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - self.erc4626.initializer(underlying_asset); - - self.entry_fee_basis_point_value.write(entry_fee); - self.entry_fee_recipient.write(entry_treasury); - self.exit_fee_basis_point_value.write(exit_fee); - self.exit_fee_recipient.write(exit_treasury); - } - - #[generate_trait] - pub impl InternalImpl of InternalTrait { - fn transfer_fees(ref self: ContractState, recipient: ContractAddress, fee: u256) { - let asset_address = self.asset(); - let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; - assert(asset_dispatcher.transfer(recipient, fee), 'Fee transfer failed'); - } - - fn remove_fee_from_deposit(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_total(assets, self.entry_fee_basis_point_value.read()); - assets - fee - } - - fn add_fee_to_mint(self: @ContractState, assets: u256) -> u256 { - assets + self.fee_on_raw(assets, self.entry_fee_basis_point_value.read()) - } - - fn add_fee_to_withdraw(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_raw(assets, self.exit_fee_basis_point_value.read()); - assets + fee - } - - fn remove_fee_from_redeem(self: @ContractState, assets: u256) -> u256 { - assets - self.fee_on_total(assets, self.exit_fee_basis_point_value.read()) - } - - /// - /// Fee operations - /// - - /// Calculates the fees that should be added to an amount `assets` that does not already - /// include fees. - /// Used in IERC4626::mint and IERC4626::withdraw operations. - fn fee_on_raw(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, Rounding::Ceil) - } - - /// Calculates the fee part of an amount `assets` that already includes fees. - /// Used in IERC4626::deposit and IERC4626::redeem operations. - fn fee_on_total(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div( - assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, Rounding::Ceil, - ) - } - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC4626Component. -The full interface includes the IERC4626, IERC20, and IERC20Metadata interfaces. -Note that implementing the IERC20Metadata interface is a requirement of IERC4626. - -``` -#[starknet::interface] -pub trait ERC4626ABI { - // IERC4626 - fn asset() -> ContractAddress; - fn total_assets() -> u256; - fn convert_to_shares(assets: u256) -> u256; - fn convert_to_assets(shares: u256) -> u256; - fn max_deposit(receiver: ContractAddress) -> u256; - fn preview_deposit(assets: u256) -> u256; - fn deposit(assets: u256, receiver: ContractAddress) -> u256; - fn max_mint(receiver: ContractAddress) -> u256; - fn preview_mint(shares: u256) -> u256; - fn mint(shares: u256, receiver: ContractAddress) -> u256; - fn max_withdraw(owner: ContractAddress) -> u256; - fn preview_withdraw(assets: u256) -> u256; - fn withdraw( - assets: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - fn max_redeem(owner: ContractAddress) -> u256; - fn preview_redeem(shares: u256) -> u256; - fn redeem( - shares: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20CamelOnly - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/erc721 - -## ERC721 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC721 - -The ERC721 token standard is a specification for non-fungible tokens, or more colloquially: NFTs. -`token::erc721::ERC721Component` provides an approximation of EIP-721 in Cairo for Starknet. - -## Usage - -Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s name, symbol, and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyNFT { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721 Mixin - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721: ERC721Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - recipient: ContractAddress - ) { - let name = "MyNFT"; - let symbol = "NFT"; - let base_uri = "https://api.example.com/v1/"; - let token_id = 1; - - self.erc721.initializer(name, symbol, base_uri); - self.erc721.mint(recipient, token_id); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC721Component. -The interface includes the IERC721 standard interface and the optional IERC721Metadata interface. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC721ABI { - // IERC721 - fn balance_of(account: ContractAddress) -> u256; - fn owner_of(token_id: u256) -> ContractAddress; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); - fn approve(to: ContractAddress, token_id: u256); - fn set_approval_for_all(operator: ContractAddress, approved: bool); - fn get_approved(token_id: u256) -> ContractAddress; - fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn token_uri(token_id: u256) -> ByteArray; - - // IERC721CamelOnly - fn balanceOf(account: ContractAddress) -> u256; - fn ownerOf(tokenId: u256) -> ContractAddress; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ); - fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); - fn setApprovalForAll(operator: ContractAddress, approved: bool); - fn getApproved(tokenId: u256) -> ContractAddress; - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721MetadataCamelOnly - fn tokenURI(tokenId: u256) -> ByteArray; -} -``` - -## ERC721 compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. -This implementation does, however, include a few notable differences such as: - -* `interface_id`s are hardcoded and initialized by the constructor. - The hardcoded values derive from Starknet’s selector calculations. - See the Introspection docs. -* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. - The difference between both functions consists of accepting `data` as an argument. - `safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. - If `data` is not used, simply pass an empty array. -* ERC721 utilizes SRC5 to declare and query interface support on Starknet as opposed to Ethereum’s EIP165. - The design for `SRC5` is similar to OpenZeppelin’s ERC165Storage. -* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). - -## Token transfers - -This library includes transfer\_from and safe\_transfer\_from to transfer NFTs. -If using `transfer_from`, **the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.** -The `safe_transfer_from` method mitigates this risk by querying the recipient contract’s interface support. - -| | | -| --- | --- | -| | Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. -The recipient contract must also implement the SRC5 interface which, as described earlier, supports interface introspection. - -### IERC721Receiver - -``` -#[starknet::interface] -pub trait IERC721Receiver { - fn on_erc721_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC721Receiver` interface exposes the on\_erc721\_received method. -When safe methods such as safe\_transfer\_from and safe\_mint are called, they invoke the recipient contract’s `on_erc721_received` method which **must** return the IERC721Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. -To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721Receiver Mixin - #[abi(embed_v0)] - impl ERC721ReceiverMixinImpl = ERC721ReceiverComponent::ERC721ReceiverMixinImpl; - impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721_receiver: ERC721ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721ReceiverEvent: ERC721ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721_receiver.initializer(); - } -} -``` - -## Storing ERC721 URIs - -Token URIs were previously stored as single field elements prior to Cairo v0.2.5. -ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the token\_uri method. -This design mirrors OpenZeppelin’s default Solidity implementation for ERC721. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/finance - -## Finance - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Finance - -This module includes primitives for financial systems. - -## Vesting component - -The VestingComponent manages the gradual release of ERC-20 tokens to a designated beneficiary based on a predefined vesting schedule. -The implementing contract must implement the OwnableComponent, where the contract owner is regarded as the vesting beneficiary. -This structure allows ownership rights of both the contract and the vested tokens to be assigned and transferred. - -| | | -| --- | --- | -| | Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning of the vesting period. As a result, if the vesting has already started, a portion of the newly transferred tokens may become immediately releasable. | - -| | | -| --- | --- | -| | By setting the duration to 0, it’s possible to configure this contract to behave like an asset timelock that holds tokens for a beneficiary until a specified date. | - -### Vesting schedule - -The VestingSchedule trait defines the logic for calculating the vested amount based on a given timestamp. This -logic is not part of the VestingComponent, so any contract implementing the VestingComponent must provide its own -implementation of the VestingSchedule trait. - -| | | -| --- | --- | -| | There’s a ready-made implementation of the VestingSchedule trait available named LinearVestingSchedule. It incorporates a cliff period by returning 0 vested amount until the cliff ends. After the cliff, the vested amount is calculated as directly proportional to the time elapsed since the beginning of the vesting schedule. | - -### Usage - -The contract must integrate VestingComponent and OwnableComponent as dependencies. The contract’s constructor -should initialize both components. Core vesting parameters, such as `beneficiary`, `start`, `duration` -and `cliff_duration`, are passed as arguments to the constructor and set at the time of deployment. - -The implementing contract must provide an implementation of the VestingSchedule trait. This can be achieved either by importing -a ready-made LinearVestingSchedule implementation or by defining a custom one. - -Here’s an example of a simple vesting wallet contract with a LinearVestingSchedule, where the vested amount -is calculated as being directly proportional to the time elapsed since the start of the vesting period. - -``` -#[starknet::contract] -mod LinearVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule}; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff_duration: u64 - ) { - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff_duration); - } -} -``` - -A vesting schedule will often follow a custom formula. In such cases, the VestingSchedule trait is useful. -To support a custom vesting schedule, the contract must provide an implementation of the -calculate\_vested\_amount function based on the desired formula. - -| | | -| --- | --- | -| | When using a custom VestingSchedule implementation, the LinearVestingSchedule must be excluded from the imports. | - -| | | -| --- | --- | -| | If there are additional parameters required for calculations, which are stored in the contract’s storage, you can access them using `self.get_contract()`. | - -Here’s an example of a vesting wallet contract with a custom VestingSchedule implementation, where tokens -are vested in a number of steps. - -``` -#[starknet::contract] -mod StepsVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait; - use openzeppelin_finance::vesting::VestingComponent; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - total_steps: u64, - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - total_steps: u64, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff: u64, - ) { - self.total_steps.write(total_steps); - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff); - } - - impl VestingSchedule of VestingScheduleTrait { - fn calculate_vested_amount( - self: @VestingComponent::ComponentState, - token: ContractAddress, - total_allocation: u256, - timestamp: u64, - start: u64, - duration: u64, - cliff: u64, - ) -> u256 { - if timestamp < cliff { - 0 - } else if timestamp >= start + duration { - total_allocation - } else { - let total_steps = self.get_contract().total_steps.read(); - let vested_per_step = total_allocation / total_steps.into(); - let step_duration = duration / total_steps; - let current_step = (timestamp - start) / step_duration; - let vested_amount = vested_per_step * current_step.into(); - vested_amount - } - } - } -} -``` - -### Interface - -Here is the full interface of a standard contract implementing the vesting functionality: - -``` -#[starknet::interface] -pub trait VestingABI { - // IVesting - fn start(self: @TState) -> u64; - fn cliff(self: @TState) -> u64; - fn duration(self: @TState) -> u64; - fn end(self: @TState) -> u64; - fn released(self: @TState, token: ContractAddress) -> u256; - fn releasable(self: @TState, token: ContractAddress) -> u256; - fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256; - fn release(ref self: TState, token: ContractAddress) -> u256; - - // IOwnable - fn owner(self: @TState) -> ContractAddress; - fn transfer_ownership(ref self: TState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TState); - - // IOwnableCamelOnly - fn transferOwnership(ref self: TState, newOwner: ContractAddress); - fn renounceOwnership(ref self: TState); -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/governance/governor - -## Governor - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Governor - -Decentralized protocols are in constant evolution from the moment they are publicly released. Often, -the initial team retains control of this evolution in the first stages, but eventually delegates it -to a community of stakeholders. The process by which this community makes decisions is called -on-chain governance, and it has become a central component of decentralized protocols, fueling -varied decisions such as parameter tweaking, smart contract upgrades, integrations with other -protocols, treasury management, grants, etc. - -This governance protocol is generally implemented in a special-purpose contract called “Governor”. In -OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different -requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, -but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. - -## Usage and setup - -### Token - -The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has -to implement the VotesComponent extension. This extension will keep track of historical balances so that voting power -is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. - -If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a -governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. - -| | | -| --- | --- | -| | The library currently does not include a wrapper for tokens, but it will be added in a future release. | - -| | | -| --- | --- | -| | Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, block numbers instead. | - -### Governor - -We will initially build a Governor without a timelock. The core logic is given by the GovernorComponent, but we -still need to choose: - -1) how voting power is determined, - -2) how many votes are needed for quorum, - -3) what options people have when casting a vote and how those votes are counted, and - -4) the execution mechanism that should be used. - -Each of these aspects is customizable by writing your own extensions, -or more easily choosing one from the library. - -**For 1)** we will use the GovernorVotes extension, which hooks to an IVotes instance to determine the voting power -of an account based on the token balance they hold when a proposal becomes active. -This module requires the address of the token to be passed as an argument to the initializer. - -**For 2)** we will use GovernorVotesQuorumFraction. This works together with the IVotes instance to define the quorum as a -percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer -parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator -is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 4%). - -**For 3)** we will use GovernorCountingSimple, an extension that offers 3 options to voters: For, Against, and Abstain, -and where only For and Abstain votes are counted towards quorum. - -**For 4)** we will use GovernorCoreExecution, an extension that allows proposal execution directly through the governor. - -| | | -| --- | --- | -| | Another option is GovernorTimelockExecution. An example can be found in the next section. | - -Besides these, we also need an implementation for the GovernorSettingsTrait defining the voting delay, voting period, -and proposal threshold. While we can use the GovernorSettings extension which allows to set these parameters by the -governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, -and proposal threshold as constant values. - -*voting\_delay*: How long after a proposal is created should voting power be fixed. A large voting delay gives -users time to unstake tokens if necessary. - -*voting\_period*: How long does a proposal remain open to votes. - -| | | -| --- | --- | -| | These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. | - -*proposal\_threshold*: This restricts proposal creation to accounts who have enough voting power. - -An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used -the `DefaultConfig`. Check the Immutable Component Config guide for more details. - -The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. - -``` -#[starknet::contract] -mod MyGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, - GovernorCoreExecutionComponent, - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM_NUMERATOR: u256 = 40; // 4% - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!( - path: GovernorVotesQuorumFractionComponent, - storage: governor_votes, - event: GovernorVotesEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorCoreExecutionComponent, - storage: governor_core_execution, - event: GovernorCoreExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl QuorumFractionImpl = - GovernorVotesQuorumFractionComponent::QuorumFractionImpl; - - // Extensions internal - impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; - impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorCoreExecutionImpl = - GovernorCoreExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_core_execution: GovernorCoreExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, votes_token: ContractAddress) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { - /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. - fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_DELAY - } - - /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. - fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_PERIOD - } - - /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. - fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { - PROPOSAL_THRESHOLD - } - } -} -``` - -### Timelock - -It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree -with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the -GovernorTimelockExecution extension. - -| | | -| --- | --- | -| | When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. | - -TimelockController uses an AccessControl setup that we need to understand in order to set up roles. - -The Proposer role is in charge of queueing operations: this is the role the Governor instance must be granted, -and it MUST be the only proposer (and canceller) in the system. - -The Executor role is in charge of executing already available operations: we can assign this role to the special -zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). - -The Canceller role is in charge of canceling operations: the Governor instance must be granted this role, -and it MUST be the only canceller in the system. - -Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. - -The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a -fixed quorum value instead of a percentage: - -``` -#[starknet::contract] -pub mod MyTimelockedGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, - GovernorTimelockExecutionComponent - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM: u256 = 100_000_000; - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); - component!( - path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorTimelockExecutionComponent, - storage: governor_timelock_execution, - event: GovernorTimelockExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; - #[abi(embed_v0)] - impl GovernorSettingsAdminImpl = - GovernorSettingsComponent::GovernorSettingsAdminImpl; - #[abi(embed_v0)] - impl TimelockedImpl = - GovernorTimelockExecutionComponent::TimelockedImpl; - - // Extensions internal - impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; - impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorTimelockExecutionImpl = - GovernorTimelockExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesComponent::Storage, - #[substorage(v0)] - pub governor_settings: GovernorSettingsComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesComponent::Event, - #[flat] - GovernorSettingsEvent: GovernorSettingsComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress - ) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token); - self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); - self.governor_timelock_execution.initializer(timelock_controller); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { - /// See `GovernorComponent::GovernorQuorumTrait::quorum`. - fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { - QUORUM - } - } -} -``` - -## Interface - -This is the full interface of the `Governor` implementation: - -``` -#[starknet::interface] -pub trait IGovernor { - fn name(self: @TState) -> felt252; - fn version(self: @TState) -> felt252; - fn COUNTING_MODE(self: @TState) -> ByteArray; - fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; - fn state(self: @TState, proposal_id: felt252) -> ProposalState; - fn proposal_threshold(self: @TState) -> u256; - fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; - fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; - fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; - fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; - fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; - fn voting_delay(self: @TState) -> u64; - fn voting_period(self: @TState) -> u64; - fn quorum(self: @TState, timepoint: u64) -> u256; - fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_votes_with_params( - self: @TState, account: ContractAddress, timepoint: u64, params: Span - ) -> u256; - fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; - fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; - fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; - fn cast_vote_with_reason( - ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray - ) -> u256; - fn cast_vote_with_reason_and_params( - ref self: TState, - proposal_id: felt252, - support: u8, - reason: ByteArray, - params: Span - ) -> u256; - fn cast_vote_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - signature: Span - ) -> u256; - fn cast_vote_with_reason_and_params_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - reason: ByteArray, - params: Span, - signature: Span - ) -> u256; - fn nonces(self: @TState, voter: ContractAddress) -> felt252; - fn relay(ref self: TState, call: Call); -} -``` - -← API Reference - -Multisig → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/governance/multisig - -## Multisig - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Multisig - -The Multisig component implements a multi-signature mechanism to enhance the security and -governance of smart contract transactions. It ensures that no single signer can unilaterally -execute critical actions, requiring multiple registered signers to approve and collectively -execute transactions. - -This component is designed to secure operations such as fund management or protocol governance, -where collective decision-making is essential. The Multisig Component is self-administered, -meaning that changes to signers or quorum must be approved through the multisig process itself. - -## Key features - -* **Multi-Signature Security**: transactions must be approved by multiple signers, ensuring - distributed governance. -* **Quorum Enforcement**: defines the minimum number of approvals required for transaction execution. -* **Self-Administration**: all modifications to the component (e.g., adding or removing signers) - must pass through the multisig process. -* **Event Logging**: provides comprehensive event logging for transparency and auditability. - -## Signer management - -The Multisig component introduces the concept of signers and quorum: - -* **Signers**: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig - Component supports adding, removing, or replacing signers. -* **Quorum**: the quorum defines the minimum number of confirmations required to approve a transaction. - -| | | -| --- | --- | -| | To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum. This ensures that all modifications pass through the multisig approval process. | - -## Transaction lifecycle - -The state of a transaction is represented by the `TransactionState` enum and can be retrieved -by calling the `get_transaction_state` function with the transaction’s identifier. - -The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash -of the transaction’s calls and salt. It can be computed by invoking the implementing contract’s -`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call -transactions. Submitting a transaction with identical calls and the same salt value a second time -will fail, as transaction identifiers must be unique. To resolve this, use a different salt value -to generate a unique identifier. - -A transaction in the Multisig component follows a specific lifecycle: - -`NotFound` → `Pending` → `Confirmed` → `Executed` - -* **NotFound**: the transaction does not exist. -* **Pending**: the transaction exists but has not reached the required confirmations. -* **Confirmed**: the transaction has reached the quorum but has not yet been executed. -* **Executed**: the transaction has been successfully executed. - -## Usage - -Integrating the Multisig functionality into a contract requires implementing MultisigComponent. -The contract’s constructor should initialize the component with a quorum value and a list of initial signers. - -Here’s an example of a simple wallet contract featuring the Multisig functionality: - -``` -#[starknet::contract] -mod MultisigWallet { - use openzeppelin_governance::multisig::MultisigComponent; - use starknet::ContractAddress; - - component!(path: MultisigComponent, storage: multisig, event: MultisigEvent); - - #[abi(embed_v0)] - impl MultisigImpl = MultisigComponent::MultisigImpl; - impl MultisigInternalImpl = MultisigComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - multisig: MultisigComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - MultisigEvent: MultisigComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, quorum: u32, signers: Span) { - self.multisig.initializer(quorum, signers); - } -} -``` - -## Interface - -This is the interface of a contract implementing the MultisigComponent: - -``` -#[starknet::interface] -pub trait MultisigABI { - // Read functions - fn get_quorum(self: @TState) -> u32; - fn is_signer(self: @TState, signer: ContractAddress) -> bool; - fn get_signers(self: @TState) -> Span; - fn is_confirmed(self: @TState, id: TransactionID) -> bool; - fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool; - fn is_executed(self: @TState, id: TransactionID) -> bool; - fn get_submitted_block(self: @TState, id: TransactionID) -> u64; - fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState; - fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32; - fn hash_transaction( - self: @TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID; - - // Write functions - fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span); - fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span); - fn replace_signer( - ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress, - ); - fn change_quorum(ref self: TState, new_quorum: u32); - fn submit_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn submit_transaction_batch( - ref self: TState, calls: Span, salt: felt252, - ) -> TransactionID; - fn confirm_transaction(ref self: TState, id: TransactionID); - fn revoke_confirmation(ref self: TState, id: TransactionID); - fn execute_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ); - fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252); -} -``` - -← Governor - -Timelock Controller → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/governance/timelock - -## Timelock Controller - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Timelock Controller - -The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. - -| | | -| --- | --- | -| | The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. | - -## Operation lifecycle - -The state of an operation is represented by the `OperationState` enum and can be retrieved -by calling the `get_operation_state` function with the operation’s identifier. - -The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the -operation’s call or calls, its predecessor, and salt. It can be computed by invoking the -implementing contract’s `hash_operation` function for single-call operations or -`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls, -predecessor, and the same salt value a second time will fail, as operation identifiers must be -unique. To resolve this, use a different salt value to generate a unique identifier. - -Timelocked operations follow a specific lifecycle: - -`Unset` → `Waiting` → `Ready` → `Done` - -* `Unset`: the operation has not been scheduled or has been canceled. -* `Waiting`: the operation has been scheduled and is pending the scheduled delay. -* `Ready`: the timer has expired, and the operation is eligible for execution. -* `Done`: the operation has been executed. - -## Timelock flow - -### Schedule - -When a proposer calls schedule, the `OperationState` moves from `Unset` to `Waiting`. -This starts a timer that must be greater than or equal to the minimum delay. -The timer expires at a timestamp accessible through get\_timestamp. -Once the timer expires, the `OperationState` automatically moves to the `Ready` state. -At this point, it can be executed. - -### Execute - -By calling execute, an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor’s operation must be in the `Done` state for this transaction to succeed. - -### Cancel - -The cancel function allows cancellers to cancel any pending operations. -This resets the operation to the `Unset` state. -It is therefore possible for a proposer to re-schedule an operation that has been cancelled. -In this case, the timer restarts when the operation is re-scheduled. - -### Roles - -TimelockControllerComponent leverages an AccessControlComponent setup that we need to understand in order to set up roles. - -* `PROPOSER_ROLE` - in charge of queueing operations. -* `CANCELLER_ROLE` - may cancel scheduled operations. - During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. - Therefore, the initial proposers may also cancel operations after they are scheduled. -* `EXECUTOR_ROLE` - in charge of executing already available operations. -* `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. - -| | | -| --- | --- | -| | The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. The latter case may be required to ease a contract’s initial configuration; however, this role should promptly be renounced. | - -Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. -This allows anyone to execute an operation once it’s in the `Ready` OperationState. -To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. - -| | | -| --- | --- | -| | Be very careful with enabling open roles as *anyone* can call the function. | - -### Minimum delay - -The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. -The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. - -After initialization, the only way to change the timelock’s minimum delay is to schedule it and execute it with the same flow as any other operation. - -The minimum delay of a contract is accessible through get\_min\_delay. - -### Usage - -Integrating the timelock into a contract requires integrating TimelockControllerComponent as well as SRC5Component and AccessControlComponent as dependencies. -The contract’s constructor should initialize the timelock which consists of setting the: - -* Proposers and executors. -* Minimum delay between scheduling and executing an operation. -* Optional admin if additional configuration is required. - -| | | -| --- | --- | -| | The optional admin should renounce their role once configuration is complete. | - -Here’s an example of a simple timelock contract: - -``` -#[starknet::contract] -mod TimelockControllerContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_governance::timelock::TimelockControllerComponent; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ContractAddress; - - component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); - component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Timelock Mixin - #[abi(embed_v0)] - impl TimelockMixinImpl = - TimelockControllerComponent::TimelockMixinImpl; - impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - access_control: AccessControlComponent::Storage, - #[substorage(v0)] - timelock: TimelockControllerComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - TimelockEvent: TimelockControllerComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - min_delay: u64, - proposers: Span, - executors: Span, - admin: ContractAddress - ) { - self.timelock.initializer(min_delay, proposers, executors, admin); - } -} -``` - -### Interface - -This is the full interface of the TimelockMixinImpl implementation: - -``` -#[starknet::interface] -pub trait TimelockABI { - // ITimelock - fn is_operation(self: @TState, id: felt252) -> bool; - fn is_operation_pending(self: @TState, id: felt252) -> bool; - fn is_operation_ready(self: @TState, id: felt252) -> bool; - fn is_operation_done(self: @TState, id: felt252) -> bool; - fn get_timestamp(self: @TState, id: felt252) -> u64; - fn get_operation_state(self: @TState, id: felt252) -> OperationState; - fn get_min_delay(self: @TState) -> u64; - fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; - fn hash_operation_batch( - self: @TState, calls: Span, predecessor: felt252, salt: felt252 - ) -> felt252; - fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); - fn schedule_batch( - ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 - ); - fn cancel(ref self: TState, id: felt252); - fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); - fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); - fn update_delay(ref self: TState, new_delay: u64); - - // ISRC5 - fn supports_interface(self: @TState, interface_id: felt252) -> bool; - - // IAccessControl - fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(self: @TState, role: felt252) -> felt252; - fn grant_role(ref self: TState, role: felt252, account: ContractAddress); - fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); - fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(self: @TState, role: felt252) -> felt252; - fn grantRole(ref self: TState, role: felt252, account: ContractAddress); - fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); - fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); -} -``` - -← Multisig - -Votes → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/governance/votes - -## Votes - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Votes - -The VotesComponent provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. - -| | | -| --- | --- | -| | By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. | - -| | | -| --- | --- | -| | The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the usage section for examples of how to implement this. | - -## Key features - -1. **Delegation**: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the delegate function directly, or by providing a signature to be used with delegate\_by\_sig. -2. **Historical lookups**: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp. - This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. - -## Usage - -When integrating the `VotesComponent`, the VotingUnitsTrait must be implemented to get the voting units for a given account as a function of the implementing contract. -For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated. -Additionally, you must implement the NoncesComponent and the SNIP12Metadata trait to enable delegation by signatures. - -Here’s an example of how to structure a simple ERC20Votes contract: - -``` -#[starknet::contract] -mod ERC20VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc20_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20VotesEvent: VotesComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - // Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. - impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { - fn after_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let mut contract_state = self.get_contract_mut(); - contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc20.initializer("MyToken", "MTK"); - } -} -``` - -And here’s an example of how to structure a simple ERC721Votes contract: - -``` -#[starknet::contract] -pub mod ERC721VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721Component; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC721 - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc721_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc721: ERC721Component::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721VotesEvent: VotesComponent::Event, - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - /// Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `before_update` hook of the - //`ERC721Component::ERC721HooksTrait`. - // This hook is called before the transfer is executed. - // This gives us access to the previous owner. - impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { - fn before_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) { - let mut contract_state = self.get_contract_mut(); - - // We use the internal function here since it does not check if the token - // id exists which is necessary for mints - let previous_owner = self._owner_of(token_id); - contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721.initializer("MyToken", "MTK", ""); - } -} -``` - -## Interface - -This is the full interface of the `VotesImpl` implementation: - -``` -#[starknet::interface] -pub trait VotesABI { - // IVotes - fn get_votes(self: @TState, account: ContractAddress) -> u256; - fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; - fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; - fn delegate(ref self: TState, delegatee: ContractAddress); - fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); - - // INonces - fn nonces(self: @TState, owner: ContractAddress) -> felt252; -} -``` - -← Timelock Controller - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/guides/deployment - -## Counterfactual deployments - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Counterfactual deployments - -A counterfactual contract is a contract we can interact with even before actually deploying it on-chain. -For example, we can send funds or assign privileges to a contract that doesn’t yet exist. -Why? Because deployments in Starknet are deterministic, allowing us to predict the address where our contract will be deployed. -We can leverage this property to make a contract pay for its own deployment by simply sending funds in advance. We call this a counterfactual deployment. - -This process can be described with the following steps: - -| | | -| --- | --- | -| | For testing this flow you can check the Starknet Foundry or the Starkli guides for deploying accounts. | - -1. Deterministically precompute the `contract_address` given a `class_hash`, `salt`, and constructor `calldata`. - Note that the `class_hash` must be previously declared for the deployment to succeed. -2. Send funds to the `contract_address`. Usually you will estimate the fee of the transaction first. Existing - tools usually do this for you. -3. Send a `DeployAccount` type transaction to the network. -4. The protocol will then validate the transaction with the `__validate_deploy__` entrypoint of the contract to be deployed. -5. If the validation succeeds, the protocol will charge the fee and then register the contract as deployed. - -| | | -| --- | --- | -| | Although this method is very popular to deploy accounts, this works for any kind of contract. | - -## Deployment validation - -To be counterfactually deployed, the deploying contract must implement the `__validate_deploy__` entrypoint, -called by the protocol when a `DeployAccount` transaction is sent to the network. - -``` -trait IDeployable { - /// Must return 'VALID' when the validation is successful. - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; -} -``` - -← Interfaces and Dispatchers - -SNIP12 and Typed Messages → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/guides/erc20-permit - -## ERC20Permit - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC20Permit - -The EIP-2612 standard, commonly referred to as ERC20Permit, is designed to support gasless token approvals. This is achieved with an off-chain -signature following the SNIP12 standard, rather than with an on-chain transaction. The permit function verifies the signature and sets -the spender’s allowance if the signature is valid. This approach improves user experience and reduces gas costs. - -## Differences from Solidity - -Although this extension is mostly similar to the Solidity implementation of EIP-2612, there are some notable differences in the parameters of the permit function: - -* The `deadline` parameter is represented by `u64` rather than `u256`. -* The `signature` parameter is represented by a span of felts rather than `v`, `r`, and `s` values. - -| | | -| --- | --- | -| | Unlike Solidity, there is no enforced format for signatures on Starknet. A signature is represented by an array or span of felts, and there is no universal method for validating signatures of unknown formats. Consequently, a signature provided to the permit function is validated through an external `is_valid_signature` call to the contract at the `owner` address. | - -## Usage - -The functionality is provided as an embeddable ERC20Permit trait of the ERC20Component. - -``` -#[abi(embed_v0)] -impl ERC20PermitImpl = ERC20Component::ERC20PermitImpl; -``` - -A contract must meet the following requirements to be able to use the ERC20Permit trait: - -* Implement ERC20Component. -* Implement NoncesComponent. -* Implement SNIP12Metadata trait (used in signature generation). - -## Typed message - -To safeguard against replay attacks and ensure the uniqueness of each approval via permit, the data signed includes: - -* The address of the `owner`. -* The parameters specified in the approve function (`spender` and `amount`) -* The address of the `token` contract itself. -* A `nonce`, which must be unique for each operation. -* The `chain_id`, which protects against cross-chain replay attacks. - -The format of the `Permit` structure in a signed permit message is as follows: - -``` -struct Permit { - token: ContractAddress, - spender: ContractAddress, - amount: u256, - nonce: felt252, - deadline: u64, -} -``` - -| | | -| --- | --- | -| | The owner of the tokens is also part of the signed message. It is used as the `signer` parameter in the `get_message_hash` call. | - -Further details on preparing and signing a typed message can be found in the SNIP12 guide. - -← Creating Supply - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/guides/erc20-supply - -## Creating ERC20 Supply - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Creating ERC20 Supply - -The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. -EIP20, from which ERC20 contracts are derived, does not specify how tokens are created. -This guide will go over strategies for creating both a fixed and dynamic token supply. - -## Fixed Supply - -Let’s say we want to create a token named `MyToken` with a fixed token supply. -We can achieve this by setting the token supply in the constructor which will execute upon deployment. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - fixed_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -In the constructor, we’re first calling the ERC20 initializer to set the token name and symbol. -Next, we’re calling the internal `mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. -Since the internal `mint` is not exposed in our contract, it will not be possible to create any more tokens. -In other words, we’ve implemented a fixed token supply! - -## Dynamic Supply - -ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. -Let’s make a few changes to the almighty `MyToken` contract and create a minting mechanism. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - } - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // This function is NOT protected which means - // ANYONE can mint tokens - self.erc20.mint(recipient, amount); - } -} -``` - -The exposed `mint` above will create `amount` tokens and allocate them to `recipient`. -We now have our minting mechanism! - -There is, however, a big problem. -`mint` does not include any restrictions on who can call this function. -For the sake of good practices, let’s implement a simple permissioning mechanism with `Ownable`. - -``` -#[starknet::contract] -mod MyToken { - - (...) - - // Integrate Ownable - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // Set permissions with Ownable - self.ownable.assert_only_owner(); - - // Mint tokens if called by the contract owner - self.erc20.mint(recipient, amount); - } -} -``` - -In the constructor, we pass the owner address to set the owner of the `MyToken` contract. -The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function. -Now, we have a protected ERC20 minting mechanism to create a dynamic token supply. - -| | | -| --- | --- | -| | For a more thorough explanation of permission mechanisms, see Access Control. | - -← ERC20 - -ERC20Permit → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/guides/snip12 - -## SNIP12 and Typed Messages - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# SNIP12 and Typed Messages - -Similar to EIP712, SNIP12 is a standard for secure off-chain signature verification on Starknet. -It provides a way to hash and sign generic typed structs rather than just strings. When building decentralized -applications, usually you might need to sign a message with complex data. The purpose of signature verification -is then to ensure that the received message was indeed signed by the expected signer, and it hasn’t been tampered with. - -OpenZeppelin Contracts for Cairo provides a set of utilities to make the implementation of this standard -as easy as possible, and in this guide we will walk you through the process of generating the hashes of typed messages -using these utilities for on-chain signature verification. For that, let’s build an example with a custom ERC20 contract -adding an extra `transfer_with_signature` method. - -| | | -| --- | --- | -| | This is an educational example, and it is not intended to be used in production environments. | - -## CustomERC20 - -Let’s start with a basic ERC20 contract leveraging the ERC20Component, and let’s add the new function. -Note that some declarations are omitted for brevity. The full example will be available at the end of the guide. - -``` -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - (...) - } -} -``` - -The `transfer_with_signature` function will allow a user to transfer tokens to another account by providing a signature. -The signature will be generated off-chain, and it will be used to verify the message on-chain. Note that the message -we need to verify is a struct with the following fields: - -* `recipient`: The address of the recipient. -* `amount`: The amount of tokens to transfer. -* `nonce`: A unique number to prevent replay attacks. -* `expiry`: The timestamp when the signature expires. - -Note that generating the hash of this message on-chain is a requirement to verify the signature, because if we accept -the message as a parameter, it could be easily tampered with. - -## Generating the Typed Message Hash - -To generate the hash of the message, we need to follow these steps: - -### 1. Define the message struct. - -In this particular example, the message struct looks like this: - -``` -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} -``` - -### 2. Get the message type hash. - -This is the `starknet_keccak(encode_type(message))` as defined in the SNIP. - -In this case it can be computed as follows: - -``` -// Since there's no u64 type in SNIP-12, we use u128 for `expiry` in the type hash generation. -let message_type_hash = selector!( - "\"Message\"(\"recipient\":\"ContractAddress\",\"amount\":\"u256\",\"nonce\":\"felt\",\"expiry\":\"u128\")\"u256\"(\"low\":\"u128\",\"high\":\"u128\")" -); -``` - -which is the same as: - -``` -let message_type_hash = 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; -``` - -| | | -| --- | --- | -| | In practice it’s better to compute the type hash off-chain and hardcode it in the contract, since it is a constant value. | - -### 3. Implement the `StructHash` trait for the struct. - -You can import the trait from: `openzeppelin_utils::snip12::StructHash`. And this implementation -is nothing more than the encoding of the message as defined in the SNIP. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::StructHash; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} -``` - -### 4. Implement the `SNIP12Metadata` trait. - -This implementation determines the values of the domain separator. Only the `name` and `version` fields are required -because the `chain_id` is obtained on-chain, and the `revision` is hardcoded to `1`. - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { 'DAPP_NAME' } - fn version() -> felt252 { 'v1' } -} -``` - -In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in -some cases we may need to read from storage to get the domain separator values. This can be accomplished even when -the trait is not bounded to the ContractState, like this: - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - let state = unsafe_new_contract_state(); - - // Some logic to get the name from storage - state.erc20.name().at(0).unwrap().into() - } - - fn version() -> felt252 { 'v1' } -} -``` - -### 5. Generate the hash. - -The final step is to use the `OffchainMessageHashImpl` implementation to generate the hash of the message -using the `get_message_hash` function. The implementation is already available as a utility. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'v1' - } -} - -fn get_hash( - account: ContractAddress, recipient: ContractAddress, amount: u256, nonce: felt252, expiry: u64 -) -> felt252 { - let message = Message { recipient, amount, nonce, expiry }; - message.get_message_hash(account) -} -``` - -| | | -| --- | --- | -| | The expected parameter for the `get_message_hash` function is the address of account that signed the message. | - -## Full Implementation - -Finally, the full implementation of the `CustomERC20` contract looks like this: - -| | | -| --- | --- | -| | We are using the `ISRC6Dispatcher` to verify the signature, and the `NoncesComponent` to handle nonces to prevent replay attacks. | - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use starknet::ContractAddress; - - use super::{Message, OffchainMessageHash, SNIP12Metadata}; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - impl NoncesInternalImpl = NoncesComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - /// Required for hash computation. - impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'CustomERC20' - } - fn version() -> felt252 { - 'v1' - } - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - assert(starknet::get_block_timestamp() <= expiry, 'Expired signature'); - let owner = starknet::get_caller_address(); - - // Check and increase nonce - self.nonces.use_checked_nonce(owner, nonce); - - // Build hash for calling `is_valid_signature` - let message = Message { recipient, amount, nonce, expiry }; - let hash = message.get_message_hash(owner); - - let is_valid_signature_felt = ISRC6Dispatcher { contract_address: owner } - .is_valid_signature(hash, signature); - - // Check either 'VALID' or true for backwards compatibility - let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED - || is_valid_signature_felt == 1; - assert(is_valid_signature, 'Invalid signature'); - - // Transfer tokens - self.erc20._transfer(owner, recipient, amount); - } -} -``` - -← Counterfactual Deployments - -Access → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/guides/src5-migration - -## Migrating ERC165 to SRC5 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Migrating ERC165 to SRC5 - -In the smart contract ecosystem, having the ability to query if a contract supports a given interface is an extremely important feature. -The initial introspection design for Contracts for Cairo before version v0.7.0 followed Ethereum’s EIP-165. -Since the Cairo language evolved introducing native types, we needed an introspection solution tailored to the Cairo ecosystem: the SNIP-5 standard. -SNIP-5 allows interface ID calculations to use Cairo types and the Starknet keccak (`sn_keccak`) function. -For more information on the decision, see the Starknet Shamans proposal or the Dual Introspection Detection discussion. - -## How to migrate - -Migrating from ERC165 to SRC5 involves four major steps: - -1. Integrate SRC5 into the contract. -2. Register SRC5 IDs. -3. Add a `migrate` function to apply introspection changes. -4. Upgrade the contract and call `migrate`. - -The following guide will go through the steps with examples. - -### Component integration - -The first step is to integrate the necessary components into the new contract. -The contract should include the new introspection mechanism, SRC5Component. -It should also include the InitializableComponent which will be used in the `migrate` function. -Here’s the setup: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_security::initializable::InitializableComponent; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - // Initializable - impl InitializableInternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -### Interface registration - -To successfully migrate ERC165 to SRC5, the contract needs to register the interface IDs that the contract supports with SRC5. - -For this example, let’s say that this contract supports the IERC721 and IERC721Metadata interfaces. -The contract should implement an `InternalImpl` and add a function to register those interfaces like this: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID}; - - (...) - - #[generate_trait] - impl InternalImpl of InternalTrait { - // Register SRC5 interfaces - fn register_src5_interfaces(ref self: ContractState) { - self.src5.register_interface(IERC721_ID); - self.src5.register_interface(IERC721_METADATA_ID); - } - } -} -``` - -Since the new contract integrates `SRC5Component`, it can leverage SRC5’s register\_interface function to register the supported interfaces. - -### Migration initializer - -Next, the contract should define and expose a migration function that will invoke the `register_src5_interfaces` function. -Since the `migrate` function will be publicly callable, it should include some sort of Access Control so that only permitted addresses can execute the migration. -Finally, `migrate` should include a reinitialization check to ensure that it cannot be called more than once. - -| | | -| --- | --- | -| | If the original contract implemented `Initializable` at any point and called the `initialize` method, the `InitializableComponent` will not be usable at this time. Instead, the contract can take inspiration from `InitializableComponent` and create its own initialization mechanism. | - -``` -#[starknet::contract] -mod MigratingContract { - (...) - - #[external(v0)] - fn migrate(ref self: ContractState) { - // WARNING: Missing Access Control mechanism. Make sure to add one - - // WARNING: If the contract ever implemented `Initializable` in the past, - // this will not work. Make sure to create a new initialization mechanism - self.initializable.initialize(); - - // Register SRC5 interfaces - self.register_src5_interfaces(); - } -} -``` - -### Execute migration - -Once the new contract is prepared for migration and **rigorously tested**, all that’s left is to migrate! -Simply upgrade the contract and then call `migrate`. - -← Introspection - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/interfaces - -## Interfaces and Dispatchers - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Interfaces and Dispatchers - -This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. - -Interfaces can be found in the module tree under the `interface` submodule, such as `token::erc20::interface`. For example: - -``` -use openzeppelin_token::erc20::interface::IERC20; -``` - -or - -``` -use openzeppelin_token::erc20::interface::ERC20ABI; -``` - -| | | -| --- | --- | -| | For simplicity, we’ll use ERC20 as example but the same concepts apply to other modules. | - -## Interface traits - -The library offers three types of traits to implement or interact with contracts: - -### Standard traits - -These are associated with a predefined interface such as a standard. -This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. - -``` -#[starknet::interface] -pub trait IERC20 { - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### ABI traits - -They describe a contract’s complete interface. This is useful to interface with a preset contract offered by this library, such as the ERC20 preset that includes functions from different traits such as `IERC20` and `IERC20Camel`. - -| | | -| --- | --- | -| | The library offers an ABI trait for most components, providing all external function signatures even when most of the time all of them don’t need to be implemented at the same time. This can be helpful when interacting with a contract implementing the component, instead of defining a new dispatcher. | - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - - // IERC20CamelOnly - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -### Dispatcher traits - -Traits annotated with `#[starknet::interface]` automatically generate a dispatcher that can be used to interact with contracts that implement the given interface. They can be imported by appending the `Dispatcher` and `DispatcherTrait` suffixes to the trait name, like this: - -``` -use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -``` - -Other types of dispatchers are also auto-generated from the annotated trait. See the -Interacting with another contract section of the Cairo book for more information. - -| | | -| --- | --- | -| | In the example, the `IERC20Dispatcher` is the one used to interact with contracts, but the `IERC20DispatcherTrait` needs to be in scope for the functions to be available. | - -## Dual interfaces - -| | | -| --- | --- | -| | `camelCase` functions are deprecated and maintained only for Backwards Compatibility. It’s recommended to only use `snake_case` interfaces with contracts and components. The `camelCase` functions will be removed in future versions. | - -Following the Great Interface Migration plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. - -In short, the library offers two types of interfaces and utilities to handle them: - -1. `camelCase` interfaces, which are the ones we’ve been using so far. -2. `snake_case` interfaces, which are the ones we’re migrating to. - -This means that currently most of our contracts implement *dual interfaces*. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. - -| | | -| --- | --- | -| | Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo (v0.6.1 and below). | - -### `IERC20` - -The default version of the ERC20 interface trait exposes `snake_case` functions: - -``` -#[starknet::interface] -pub trait IERC20 { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### `IERC20Camel` - -On top of that, the library also offers a `camelCase` version of the same interface: - -``` -#[starknet::interface] -pub trait IERC20Camel { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -← Presets - -Counterfactual Deployments → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/introspection - -## Introspection - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Introspection - -To smooth interoperability, often standards require smart contracts to implement introspection mechanisms. - -In Ethereum, the EIP165 standard defines how contracts should declare -their support for a given interface, and how other contracts may query this support. - -Starknet offers a similar mechanism for interface introspection defined by the SRC5 standard. - -## SRC5 - -Similar to its Ethereum counterpart, the SRC5 standard requires contracts to implement the `supports_interface` function, -which can be used by others to query if a given interface is supported. - -### Usage - -To expose this functionality, the contract must implement the SRC5Component, which defines the `supports_interface` function. -Here is an example contract: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.src5.register_interface(selector!("some_interface")); - } -} -``` - -### Interface - -``` -#[starknet::interface] -pub trait ISRC5 { - /// Query if a contract implements an interface. - /// Receives the interface identifier as specified in SRC-5. - /// Returns `true` if the contract implements `interface_id`, `false` otherwise. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -## Computing the interface ID - -The interface ID, as specified in the standard, is the XOR of all the -Extended Function Selectors -of the interface. We strongly advise reading the SNIP to understand the specifics of computing these -extended function selectors. There are tools such as src5-rs that can help with this process. - -## Registering interfaces - -For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - // Register the contract's support for the ISRC6 interface - self.src5.register_interface(interface::ISRC6_ID); - } - - (...) -} -``` - -## Querying interfaces - -Use the `supports_interface` function to query a contract’s support for a given interface. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::interface::ISRC5DispatcherTrait; - use openzeppelin_introspection::interface::ISRC5Dispatcher; - use starknet::ContractAddress; - - #[storage] - struct Storage {} - - #[external(v0)] - fn query_is_account(self: @ContractState, target: ContractAddress) -> bool { - let dispatcher = ISRC5Dispatcher { contract_address: target }; - dispatcher.supports_interface(interface::ISRC6_ID) - } -} -``` - -| | | -| --- | --- | -| | If you are unsure whether a contract implements SRC5 or not, you can follow the process described in here. | - -← API Reference - -Migrating ERC165 to SRC5 → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/macros - -## Macros - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Macros - -This crate provides a collection of macros that streamline and simplify development with the library. -To use them, you need to add the `openzeppelin_macros` crate as a dependency in your `Scarb.toml` file: - -``` -[dependencies] -openzeppelin_macros = "2.0.0-alpha.1" -``` - -## Attribute macros - -* with\_components -* type\_hash - -← API Reference - -with\_components → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/macros/type_hash - -## type_hash - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# `type_hash` - -This macro generates a SNIP-12-compatible type hash for a given struct or enum. - -| | | -| --- | --- | -| | This macro is fully compatible with the SNIP-12 standard revision 1. | - -## Usage - -``` -/// name and debug are optional arguments -#[type_hash(name: "My Struct", debug: true)] -struct MyStruct { - #[snip12(name: "My Field")] - my_field: felt252, -} -``` - -This will generate a type hash for the struct. - -``` -// Encoded type: "My Struct"("My Field":"felt") -pub const MY_STRUCT_TYPE_HASH: felt252 = 0x1735aa9819941b96c651b740b792a96c854565eaff089b7e293d996828b88a8; -``` - -And because of the `debug` argument, it will generate the following code: - -``` -pub fn __MY_STRUCT_encoded_type() { - println!("\"My Struct\"(\"My Field\":\"felt\")"); -} -``` - -## Basic types - -The list of supported basic types as defined in the SNIP-12 standard is: - -* felt252 -* shortstring -* ClassHash -* ContractAddress -* timestamp -* selector -* merkletree -* u128 -* i128 - -### Examples - -Struct with basic types and custom names and kinds: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Simple Felt")] // Optional custom name - pub simple_felt: felt252, - #[snip12(name: "Class Hash")] - pub class_hash: ClassHash, - #[snip12(name: "Target Token")] - pub target: ContractAddress, - #[snip12(name: "Timestamp", kind: "timestamp")] - pub timestamp: u128, - #[snip12(name: "Selector", kind: "selector")] - pub selector: felt252, -} - -// Encoded type: "My Struct"("Simple Felt":"felt","Class Hash":"ClassHash", -// "Target Token":"ContractAddress","Timestamp":"timestamp","Selector":"selector") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x522e0c3dc5e13b0978f4645760a436b1e119fd335842523fee8fbae6057b8c; -``` - -Enum with basic types and custom names and kinds: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Simple Felt")] - SimpleFelt: felt252, - #[snip12(name: "Class Hash")] - ClassHash: ClassHash, - #[snip12(name: "Target Token")] - ContractAddress: ContractAddress, - #[snip12(name: "Timestamp", kind: "timestamp")] - Timestamp: u128, - #[snip12(name: "Selector", kind: "selector")] - Selector: felt252, -} - -// Encoded type: "My Enum"("Simple Felt"("felt"),"Class Hash"("ClassHash"), -// "Target Token"("ContractAddress"),"Timestamp"("timestamp"),"Selector"("selector")) -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x3f30aaa6cda9f699d4131940b10602b78b986feb88f28a19f3b48567cb4b566; -``` - -## Collection types - -The list of supported collection types as defined in the SNIP-12 standard is: - -* Array -* Tuple **(Only supported for enums)** -* Span **(Treated as an array)** - -| | | -| --- | --- | -| | While Span is not directly supported by the SNIP-12 standard, it is treated as an array for the purposes of this macro, since it is sometimes helpful to use `Span` instead of `Array` in order to save on gas. | - -### Examples - -Struct with collection types: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Member 1")] - pub member1: Array, - #[snip12(name: "Member 2")] - pub member2: Span, - #[snip12(name: "Timestamps", kind: "Array")] - pub timestamps: Array, -} - -// Encoded type: "My Struct"("Member 1":"felt*","Member 2":"u128*", -// "Timestamps":"timestamp*") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x369cdec45d8c55e70986aed44da0e330375171ba6e25b58e741c0ce02fa8ac; -``` - -Enum with collection types: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Member 1")] - Member1: Array, - #[snip12(name: "Member 2")] - Member2: Span, - #[snip12(name: "Timestamps", kind: "Array")] - Timestamps: Array, - #[snip12(name: "Name and Last Name", kind: "(shortstring, shortstring)")] - NameAndLastName: (felt252, felt252), -} - -// Encoded type: "My Enum"("Member 1"("felt*"),"Member 2"("u128*"), -// "Timestamps"("timestamp*"),"Name and Last Name"("shortstring","shortstring")) -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x9e3e1ebad4448a8344b3318f9cfda5df237588fd8328e1c2968635f09c735d; -``` - -## Preset types - -The list of supported preset types as defined in the SNIP-12 standard is: - -* TokenAmount -* NftId -* u256 - -### Examples - -Struct with preset types: - -``` -#[type_hash(name: "My Struct", debug: true)] -pub struct MyStruct { - #[snip12(name: "Token Amount")] - pub token_amount: TokenAmount, - #[snip12(name: "NFT ID")] - pub nft_id: NftId, - #[snip12(name: "Number")] - pub number: u256, -} - -// Encoded type: "My Struct"("Token Amount":"TokenAmount","NFT ID":"NftId","Number":"u256")"NftId" -// ("collection_address":"ContractAddress","token_id":"u256")"TokenAmount" -// ("token_address":"ContractAddress","amount":"u256") -// "u256"("low":"u128","high":"u128") -pub const MY_STRUCT_TYPE_HASH: felt252 - = 0x19f63528d68c4f44b7d9003a5a6b7793f5bb6ffc8a22bdec82b413ddf4f9412; -``` - -Enum with preset types: - -``` -#[type_hash(name: "My Enum", debug: true)] -pub enum MyEnum { - #[snip12(name: "Token Amount")] - TokenAmount: TokenAmount, - #[snip12(name: "NFT ID")] - NftId: NftId, - #[snip12(name: "Number")] - Number: u256, -} - -// Encoded type: "My Enum"("Token Amount"("TokenAmount"),"NFT ID"("NftId"),"Number"("u256"))"NftId" -// ("collection_address":"ContractAddress","token_id":"u256")"TokenAmount" -// ("token_address":"ContractAddress","amount":"u256") -// "u256"("low":"u128","high":"u128") -pub const MY_ENUM_TYPE_HASH: felt252 - = 0x39dd19c7e5c5f89e084b78a26200b712c6ae3265f2bae774471c588858421b7; -``` - -## User-defined types - -User-defined types are currently **NOT SUPPORTED** since the macro doesn’t have access to scope outside of the -target struct/enum. In the future it may be supported by extending the syntax to explicitly declare the custom type -definition. - -← with\_components - -Merkle Tree → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/macros/with_components - -## with_components - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# `with_components` - -This macro simplifies the syntax for adding a set of components to a contract. It: - -* *Imports the corresponding components into the contract.* -* *Adds the corresponding `component!` macro entries.* -* *Adds the storage entries for each component to the Storage struct.* -* *Adds the event entries for each component to the Event struct, or creates the struct if it is missing.* -* *Brings the corresponding internal implementations into scope.* -* *Provides some diagnostics for each specific component to help the developer avoid common mistakes.* - -| | | -| --- | --- | -| | Since the macro does not expose any external implementations, developers must make sure to specify explicitly the ones required by the contract. | - -## Security considerations - -The macro was designed to be simple and effective while still being very hard to misuse. For this reason, the features -that it provides are limited, and things that might make the contract behave in unexpected ways must be -explicitly specified by the developer. It does not specify external implementations, so contracts won’t find -themselves in a situation where external functions are exposed without the developer’s knowledge. It brings -the internal implementations into scope so these functions are available by default, but if they are not used, -they won’t have any effect on the contract’s behavior. - -## Usage - -This is how a contract with multiple components looks when using the macro. - -``` -#[with_components(Account, SRC5, SRC9, Upgradeable)] -#[starknet::contract(account)] -mod OutsideExecutionAccountUpgradeable { - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ClassHash, ContractAddress}; - - // External - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - #[abi(embed_v0)] - impl OutsideExecutionV2Impl = - SRC9Component::OutsideExecutionV2Impl; - - #[storage] - struct Storage {} - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - self.src9.initializer(); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.account.assert_only_self(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -This is how the same contract looks using regular syntax. - -``` -#[starknet::contract(account)] -mod OutsideExecutionAccountUpgradeable { - use openzeppelin::account::AccountComponent; - use openzeppelin::account::extensions::SRC9Component; - use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::upgrades::UpgradeableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; - use starknet::ClassHash; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: SRC9Component, storage: src9, event: SRC9Event); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // External - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - #[abi(embed_v0)] - impl OutsideExecutionV2Impl = - SRC9Component::OutsideExecutionV2Impl; - - // Internal - impl AccountInternalImpl = AccountComponent::InternalImpl; - impl OutsideExecutionInternalImpl = SRC9Component::InternalImpl; - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - src9: SRC9Component::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - SRC9Event: SRC9Component::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - self.src9.initializer(); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.account.assert_only_self(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -← Macros - -type\_hash → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/presets - -## Presets - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Presets - -Presets are ready-to-deploy contracts provided by the library. Since presets are intended to be very simple -and as generic as possible, there’s no support for custom or complex contracts such as `ERC20Pausable` or `ERC721Mintable`. - -| | | -| --- | --- | -| | For contract customization and combination of modules you can use Wizard for Cairo, our code-generation tool. | - -## Available presets - -List of available presets and their corresponding Sierra class hashes. Like Contracts for Cairo, -use of preset contracts are subject to the terms of the -MIT License. - -| | | -| --- | --- | -| | Class hashes were computed using cairo 2.11.4. | - -| Name | Sierra Class Hash | -| --- | --- | -| `AccountUpgradeable` | `0x07fa937960fd981bc9a7f54f02786cfa6c6f194fc66cb0c35c1588bd83448062` | -| `ERC20Upgradeable` | `0x05ae0a6a994b2145a80c31fb3cd46f7150d984de8e104becdebe481d7724daf3` | -| `ERC721Upgradeable` | `0x0077dcbd0d9907cff8b84dcf0c3006ab07b27a7db1e1e4e12b272d6b1fcdad4c` | -| `ERC1155Upgradeable` | `0x019f291ac71b768cef21602a19bedbc2f45d38374bba086585cd434c2c0e28cd` | -| `EthAccountUpgradeable` | `0x06c71d751a10084fa31758b50348bfaa7f0b8e4b1ce36c2ab5b159cb4c307f74` | -| `UniversalDeployer` | `0x025cc49fb4b211e46b3b91bfbdd4741202ca371cd25abe2806d1b5e1250e1759` | -| `VestingWallet` | `0x01865aa64d7cbc465ab675d87b493c4c58af82eef726e702d87ca8ca4f6040e2` | - -| | | -| --- | --- | -| | starkli class-hash command can be used to compute the class hash from a Sierra artifact. | - -## Usage - -These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. -Simply deploy the preset class hash and add the appropriate constructor arguments. -Deploying the ERC20Upgradeable preset with starkli, for example, will look like this: - -``` -starkli deploy 0x05ae0a6a994b2145a80c31fb3cd46f7150d984de8e104becdebe481d7724daf3 \ - \ - --network="sepolia" -``` - -If a class hash has yet to be declared, copy/paste the preset contract code and declare it locally. -Start by setting up a project and installing the Contracts for Cairo library. -Copy the target preset contract from the presets directory and paste it in the new project’s `src/lib.cairo` like this: - -``` -// src/lib.cairo - -#[starknet::contract] -mod ERC20Upgradeable { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ContractAddress, ClassHash}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Upgradeable - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress, - owner: ContractAddress - ) { - self.ownable.initializer(owner); - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -Next, compile the contract. - -``` -scarb build -``` - -Finally, declare the preset. - -``` -starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ - --network="sepolia" -``` - -← Components - -Interfaces and Dispatchers → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/security - -## Security - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Security - -The following documentation provides context, reasoning, and examples of modules found under `openzeppelin_security`. - -| | | -| --- | --- | -| | Expect these modules to evolve. | - -## Initializable - -The Initializable component provides a simple mechanism that mimics -the functionality of a constructor. -More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. - -### Usage - -You can use the component in your contracts like this: - -``` -#[starknet::contract] -mod MyInitializableContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - impl InternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage, - param: felt252 - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } - - fn initializer(ref self: ContractState, some_param: felt252) { - // Makes the method callable only once - self.initializable.initialize(); - - // Initialization logic - self.param.write(some_param); - } -} -``` - -| | | -| --- | --- | -| | This Initializable pattern should only be used in one function. | - -### Interface - -The component provides the following external functions as part of the `InitializableImpl` implementation: - -``` -#[starknet::interface] -pub trait InitializableABI { - fn is_initialized() -> bool; -} -``` - -## Pausable - -The Pausable component allows contracts to implement an emergency stop mechanism. -This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. - -To become pausable, the contract should include `pause` and `unpause` functions (which should be protected). -For methods that should be available only when paused or not, insert calls to `assert_paused` and `assert_not_paused` -respectively. - -### Usage - -For example (using the Ownable component for access control): - -``` -#[starknet::contract] -mod MyPausableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_security::PausableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // Pausable - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - pausable: PausableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - PausableEvent: PausableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[external(v0)] - fn pause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.pause(); - } - - #[external(v0)] - fn unpause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.unpause(); - } - - #[external(v0)] - fn when_not_paused(ref self: ContractState) { - self.pausable.assert_not_paused(); - // Do something - } - - #[external(v0)] - fn when_paused(ref self: ContractState) { - self.pausable.assert_paused(); - // Do something - } -} -``` - -### Interface - -The component provides the following external functions as part of the `PausableImpl` implementation: - -``` -#[starknet::interface] -pub trait PausableABI { - fn is_paused() -> bool; -} -``` - -## Reentrancy Guard - -A reentrancy attack occurs when the caller is able to obtain more resources than allowed by recursively calling a target’s function. - -### Usage - -Since Cairo does not support modifiers like Solidity, the ReentrancyGuard -component exposes two methods `start` and `end` to protect functions against reentrancy attacks. -The protected function must call `start` before the first function statement, and `end` before the return statement, as shown below: - -``` -#[starknet::contract] -mod MyReentrancyContract { - use openzeppelin_security::ReentrancyGuardComponent; - - component!( - path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent - ); - - impl InternalImpl = ReentrancyGuardComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - reentrancy_guard: ReentrancyGuardComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ReentrancyGuardEvent: ReentrancyGuardComponent::Event - } - - #[external(v0)] - fn protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } - - #[external(v0)] - fn another_protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } -} -``` - -| | | -| --- | --- | -| | The guard prevents the execution flow occurring inside `protected_function` to call itself or `another_protected_function`, and vice versa. | - -← Merkle Tree - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/udc - -## Universal Deployer Contract - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Universal Deployer Contract - -The Universal Deployer Contract (UDC) is a singleton smart contract that wraps the deploy syscall to expose it to any contract that doesn’t implement it, such as account contracts. You can think of it as a standardized generic factory for Starknet contracts. - -Since Starknet has no deployment transaction type, it offers a standardized way to deploy smart contracts by following the Standard Deployer Interface and emitting a ContractDeployed event. - -For details on the motivation and the decision making process, see the Universal Deployer Contract proposal. - -## UDC contract address - -This version of the UDC is not deployed yet. Check the current deployed version here. - -## Interface - -``` -#[starknet::interface] -pub trait IUniversalDeployer { - fn deploy_contract( - class_hash: ClassHash, - salt: felt252, - from_zero: bool, - calldata: Span - ) -> ContractAddress; -} -``` - -## Deploying a contract with the UDC - -First, declare the target contract (if it’s not already declared). -Next, call the UDC’s `deploy_contract` method. -Here’s an implementation example in Cairo: - -``` -use openzeppelin_utils::interfaces::{IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait}; - -const UDC_ADDRESS: felt252 = 0x04...; - -fn deploy() -> ContractAddress { - let dispatcher = IUniversalDeployerDispatcher { - contract_address: UDC_ADDRESS.try_into().unwrap() - }; - - // Deployment parameters - let class_hash = class_hash_const::< - 0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6 - >(); - let salt = 1234567879; - let not_from_zero = true; - let calldata = array![]; - - // The UDC returns the deployed contract address - dispatcher.deploy_contract(class_hash, salt, not_from_zero, calldata.span()) -} -``` - -## Deployment types - -The Universal Deployer Contract offers two types of addresses to deploy: origin-dependent and origin-independent. -As the names suggest, the origin-dependent type includes the deployer’s address in the address calculation, -whereas, the origin-independent type does not. -The `from_zero` boolean parameter ultimately determines the type of deployment. - -| | | -| --- | --- | -| | When deploying a contract that uses `get_caller_address` in the constructor calldata, remember that the UDC, not the account, deploys that contract. Therefore, querying `get_caller_address` in a contract’s constructor returns the UDC’s address, *not the account’s address*. | - -### Origin-dependent - -By making deployments dependent upon the origin address, users can reserve a whole address space to prevent someone else from taking ownership of the address. - -Only the owner of the origin address can deploy to those addresses. - -Achieving this type of deployment necessitates that the origin sets `from_zero` to `false` in the deploy\_contract call. -Under the hood, the function passes a modified salt to the `deploy_syscall`, which is the hash of the origin’s address with the given salt. - -To deploy a unique contract address pass: - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, true, calldata.span()); -``` - -### Origin-independent - -Origin-independent contract deployments create contract addresses independent of the deployer and the UDC instance. -Instead, only the class hash, salt, and constructor arguments determine the address. -This type of deployment enables redeployments of accounts and known systems across multiple networks. -To deploy a reproducible deployment, set `from_zero` to `true`. - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, false, calldata.span()); -``` - -## Version changes - -| | | -| --- | --- | -| | See the previous Universal Deployer API for the initial spec. | - -The latest iteration of the UDC includes some notable changes to the API which include: - -* `deployContract` method is replaced with the snake\_case deploy\_contract. -* `unique` parameter is replaced with `not_from_zero` in both the `deploy_contract` method and ContractDeployed event. - -## Precomputing contract addresses - -This library offers utility functions written in Cairo to precompute contract addresses. -They include the generic calculate\_contract\_address\_from\_deploy\_syscall as well as the UDC-specific calculate\_contract\_address\_from\_udc. -Check out the deployments for more information. - -← Common - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/upgrades - -## Upgrades - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Upgrades - -In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns. - -Starknet has native upgradeability through a syscall that updates the contract source code, removing the need for proxies. - -| | | -| --- | --- | -| | Make sure you follow our security recommendations before upgrading. | - -## Replacing contract classes - -To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class. - -Contract Classes represent the source code of a program. All contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by a class hash, and before a contract of a class can be deployed, the class hash needs to be declared. - -### `replace_class_syscall` - -The `replace_class` syscall allows a contract to update its source code by replacing its class hash once deployed. - -``` -/// Upgrades the contract source code to the new contract class. -fn upgrade(new_class_hash: ClassHash) { - assert(!new_class_hash.is_zero(), 'Class hash cannot be zero'); - starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); -} -``` - -| | | -| --- | --- | -| | If a contract is deployed without this mechanism, its class hash can still be replaced through library calls. | - -## `Upgradeable` component - -OpenZeppelin Contracts for Cairo provides Upgradeable to add upgradeability support to your contracts. - -### Usage - -Upgrades are often very sensitive operations, and some form of access control is usually required to -avoid unauthorized upgrades. The Ownable module is used in this example. - -| | | -| --- | --- | -| | We will be using the following module to implement the IUpgradeable interface described in the API Reference section. | - -``` -#[starknet::contract] -mod UpgradeableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::ClassHash; - use starknet::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; - - #[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 - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[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(); - - // Replace the class hash upgrading the contract - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -## Security - -Upgrades can be very sensitive operations, and security should always be top of mind while performing one. Please make sure you thoroughly review the changes and their consequences before upgrading. Some aspects to consider are: - -* API changes that might affect integration. For example, changing an external function’s arguments might break existing contracts or offchain systems calling your contract. -* Storage changes that might result in lost data (e.g. changing a storage slot name, making existing storage inaccessible). -* Collisions (e.g. mistakenly reusing the same storage slot from another component) are also possible, although less likely if best practices are followed, for example prepending storage variables with the component’s name (e.g. `ERC20_balances`). -* Always check for backwards compatibility before upgrading between versions of OpenZeppelin Contracts. - -## Proxies in Starknet - -Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them. - -In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement. - -Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used -for redirecting every potential function call to the implementation. This means that a generic proxy contract -can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward -their execution to another contract class. -This can still be useful for example to upgrade the logic of some functions. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/wizard - -## Wizard for Cairo - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Wizard for Cairo - -Not sure where to start? Use the interactive generator below to bootstrap your -contract and learn about the components offered in OpenZeppelin Contracts for Cairo. - -| | | -| --- | --- | -| | We strongly recommend checking the Components section to understand how to extend from our library. | - -← Overview - -Components → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/ - -## Contracts for Cairo - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Contracts for Cairo - -**A library for secure smart contract development** written in Cairo for Starknet. This library consists of a set of reusable components to build custom smart contracts, as well as -ready-to-deploy presets. You can also find other utilities including interfaces and dispatchers and test utilities -that facilitate testing with Starknet Foundry. - -| | | -| --- | --- | -| | This repo contains highly experimental code. Expect rapid iteration. **Use at your own risk.** | - -| | | -| --- | --- | -| | You can track our roadmap and future milestones in our Github Project. | - -## Installation - -The library is available as a Scarb package. Follow this guide for installing Cairo and Scarb on your machine -before proceeding, and run the following command to check that the installation was successful: - -``` -$ scarb --version - -scarb 2.11.3 (15764158b 2025-03-13) -cairo: 2.11.2 (https://crates.io/crates/cairo-lang-compiler/2.11.2) -sierra: 1.7.0 -``` - -### Set up your project - -Create an empty directory, and `cd` into it: - -``` -mkdir my_project/ && cd my_project/ -``` - -Initialize a new Scarb project: - -``` -scarb init -``` - -The contents of `my_project/` should now look like this: - -``` -$ ls - -Scarb.toml src -``` - -### Install the library - -Install the library by declaring it as a dependency in the project’s `Scarb.toml` file: - -| | | -| --- | --- | -| | Alpha releases are not deployed to scarbs.xyz. To add the library as a dependency, it should be declared as a git dependency. | - -``` -[dependencies] -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v2.0.0-alpha.0" } -``` - -The previous example would import the entire library. We can also add each package as a separate dependency to -improve the building time by not including modules that won’t be used: - -``` -[dependencies] -openzeppelin_access = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v2.0.0-alpha.0" } -openzeppelin_token = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v2.0.0-alpha.0" } -``` - -## Basic usage - -This is how it looks to build an ERC20 contract using the ERC20 component. -Copy the code into `src/lib.cairo`. - -``` -#[starknet::contract] -mod MyERC20Token { - // NOTE: If you added the entire library as a dependency, - // use `openzeppelin::token` instead. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -You can now compile it: - -``` -scarb build -``` - -Wizard → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/access - -## Access - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Access - -Access control—​that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. -The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. -It is therefore critical to understand how you implement it, lest someone else -steals your whole system. - -## Ownership and `Ownable` - -The most common and basic form of access control is the concept of ownership: there’s an account that is the `owner` -of a contract and can do administrative tasks on it. -This approach is perfectly reasonable for contracts that have a single administrative user. - -OpenZeppelin Contracts for Cairo provides OwnableComponent for implementing ownership in your contracts. - -### Usage - -Integrating this component into a contract first requires assigning an owner. -The implementing contract’s constructor should set the initial owner by passing the owner’s address to Ownable’s -`initializer` like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Set the initial owner of the contract - self.ownable.initializer(owner); - } - - (...) -} -``` - -To restrict a function’s access to the owner only, add in the `assert_only_owner` method: - -``` -#[starknet::contract] -mod MyContract { - (...) - - #[external(v0)] - fn only_owner_allowed(ref self: ContractState) { - // This function can only be called by the owner - self.ownable.assert_only_owner(); - - (...) - } -} -``` - -### Interface - -This is the full interface of the `OwnableMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableABI { - // IOwnable - fn owner() -> ContractAddress; - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableCamelOnly - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -Ownable also lets you: - -* `transfer_ownership` from the owner account to a new one, and -* `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern - after an initial stage with centralized administration is over. - -| | | -| --- | --- | -| | Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` will no longer be callable! | - -### Two step transfer - -The component also offers a more robust way of transferring ownership via the -OwnableTwoStepImpl implementation. A two step transfer mechanism helps -to prevent unintended and irreversible owner transfers. Simply replace the `OwnableMixinImpl` -with its respective two step variant: - -``` -#[abi(embed_v0)] -impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl; -``` - -#### Interface - -This is the full interface of the two step `OwnableTwoStepMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait OwnableTwoStepABI { - // IOwnableTwoStep - fn owner() -> ContractAddress; - fn pending_owner() -> ContractAddress; - fn accept_ownership(); - fn transfer_ownership(new_owner: ContractAddress); - fn renounce_ownership(); - - // IOwnableTwoStepCamelOnly - fn pendingOwner() -> ContractAddress; - fn acceptOwnership(); - fn transferOwnership(newOwner: ContractAddress); - fn renounceOwnership(); -} -``` - -## Role-Based `AccessControl` - -While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of -authorization are often needed. You may want for an account to have permission to ban users from a system, but not -create new tokens. Role-Based Access Control (RBAC) offers -flexibility in this regard. - -In essence, we will be defining multiple roles, each allowed to perform different sets of actions. -An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for -instead of simply using `assert_only_owner`. This check can be enforced through `assert_only_role`. -Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. - -Most software uses access control systems that are role-based: some users are regular users, some may be supervisors -or managers, and a few will often have administrative privileges. - -### Usage - -For each role that you want to define, you will create a new *role identifier* that is used to grant, revoke, and -check if an account has that role. See Creating role identifiers for information -on creating identifiers. - -Here’s a simple example of implementing AccessControl on a portion of an ERC20 token contract which defines -and sets a 'minter' role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::MINTER_ROLE; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } -} -``` - -| | | -| --- | --- | -| | Make sure you fully understand how AccessControl works before using it on your system, or copy-pasting the examples from this guide. | - -While clear and explicit, this isn’t anything we wouldn’t have been able to achieve with -Ownable. Where AccessControl shines the most is in scenarios where granular -permissions are required, which can be implemented by defining *multiple* roles. - -Let’s augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - minter: ContractAddress, - burner: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(MINTER_ROLE, minter); - self.accesscontrol._grant_role(BURNER_ROLE, burner); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -So clean! -By splitting concerns this way, more granular levels of permission may be implemented than were possible with the -simpler ownership approach to access control. Limiting what each component of a system is able to do is known -as the principle of least privilege, and is a good -security practice. Note that each account may still have more than one role, if so desired. - -### Granting and revoking roles - -The ERC20 token example above uses `_grant_role`, -an `internal` function that is useful when programmatically assigning -roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? - -By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making -the `assert_only_role` check pass. To grant and revoke roles dynamically, you will need help from the role’s *admin*. - -Every role has an associated admin role, which grants permission to call the -`grant_role` and -`revoke_role` functions. -A role can be granted or revoked by using these if the calling account has the corresponding admin role. -Multiple roles may have the same admin role to make management easier. -A role’s admin can even be the same role itself, which would cause accounts with that role to be able -to also grant and revoke it. - -This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also -provides an easy way to manage simpler applications. `AccessControl` includes a special role with the role identifier -of `0`, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. -An account with this role will be able to manage any other role, unless -`set_role_admin` is used to select a new admin role. - -Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: - -``` -const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); -const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); - -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - use super::{MINTER_ROLE, BURNER_ROLE}; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress, - admin: ContractAddress - ) { - // ERC20-related initialization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - - // AccessControl-related initialization - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin); - } - - /// This function can only be called by a minter. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); - } - - /// This function can only be called by a burner. - #[external(v0)] - fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(BURNER_ROLE); - self.erc20.burn(account, amount); - } -} -``` - -| | | -| --- | --- | -| | The `grant_role` and `revoke_role` functions are automatically exposed as `external` functions from the `AccessControlImpl` by leveraging the `#[abi(embed_v0)]` annotation. | - -Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. -However, because those roles' admin role is the default admin role, and that role was granted to the 'admin', that -same account can call `grant_role` to give minting or burning permission, and `revoke_role` to remove it. - -Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary -over time. It can also be used to support use cases such as KYC, -where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -### Creating role identifiers - -In the Solidity implementation of AccessControl, contracts generally refer to the -keccak256 hash -of a role as the role identifier. - -For example: - -``` -bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") -``` - -These identifiers take up 32 bytes (256 bits). - -Cairo field elements (`felt252`) store a maximum of 252 bits. -With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. -Some ideas to consider: - -* Use sn\_keccak instead. -* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the - Cairo corelib. - -| | | -| --- | --- | -| | The `selector!` macro can be used to compute sn\_keccak in Cairo. | - -### Interface - -This is the full interface of the `AccessControlMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccessControlABI { - // IAccessControl - fn has_role(role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(role: felt252) -> felt252; - fn grant_role(role: felt252, account: ContractAddress); - fn revoke_role(role: felt252, account: ContractAddress); - fn renounce_role(role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(role: felt252) -> felt252; - fn grantRole(role: felt252, account: ContractAddress); - fn revokeRole(role: felt252, account: ContractAddress); - fn renounceRole(role: felt252, account: ContractAddress); - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -`AccessControl` also lets you `renounce_role` from the calling account. -The method expects an account as input as an extra security measure, to ensure you are -not renouncing a role from an unintended account. - -← SNIP12 and Typed Messages - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/accounts - -## Accounts - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Accounts - -Unlike Ethereum where accounts are derived from a private key, all Starknet accounts are contracts. This means there’s no Externally Owned Account (EOA) -concept on Starknet. - -Instead, the network features native account abstraction and signature validation happens at the contract level. - -For a general overview of account abstraction, see -Starknet’s documentation. -A more detailed discussion on the topic can be found in -Starknet Shaman’s forum. - -| | | -| --- | --- | -| | For detailed information on the usage and implementation check the API Reference section. | - -## What is an account? - -Accounts in Starknet are smart contracts, and so they can be deployed and interacted -with like any other contract, and can be extended to implement any custom logic. However, an account is a special type -of contract that is used to validate and execute transactions. For this reason, it must implement a set of entrypoints -that the protocol uses for this execution flow. The SNIP-6 proposal defines a standard interface for accounts, -supporting this execution flow and interoperability with DApps in the ecosystem. - -### ISRC6 Interface - -``` -/// Represents a call to a target contract function. -struct Call { - to: ContractAddress, - selector: felt252, - calldata: Span -} - -/// Standard Account Interface -#[starknet::interface] -pub trait ISRC6 { - /// Executes a transaction through the account. - fn __execute__(calls: Array); - - /// Asserts whether the transaction is valid to be executed. - fn __validate__(calls: Array) -> felt252; - - /// Asserts whether a given signature for a given hash is valid. - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; -} -``` - -| | | -| --- | --- | -| | The `calldata` member of the `Call` struct in the accounts has been updated to `Span` for optimization purposes, but the interface ID remains the same for backwards compatibility. This inconsistency will be fixed in future releases. | - -SNIP-6 adds the `is_valid_signature` method. This method is not used by the protocol, but it’s useful for -DApps to verify the validity of signatures, supporting features like Sign In with Starknet. - -SNIP-6 also defines that compliant accounts must implement the SRC5 interface following SNIP-5, as -a mechanism for detecting whether a contract is an account or not through introspection. - -### ISRC5 Interface - -``` -/// Standard Interface Detection -#[starknet::interface] -pub trait ISRC5 { - /// Queries if a contract implements a given interface. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -SNIP-6 compliant accounts must return `true` when queried for the ISRC6 interface ID. - -Even though these interfaces are not enforced by the protocol, it’s recommended to implement them for enabling -interoperability with the ecosystem. - -### Protocol-level methods - -The Starknet protocol uses a few entrypoints for abstracting the accounts. We already mentioned the first two -as part of the ISRC6 interface, and both are required for enabling accounts to be used for executing transactions. The rest are optional: - -1. `__validate__` verifies the validity of the transaction to be executed. This is usually used to validate signatures, - but the entrypoint implementation can be customized to feature any validation mechanism with some limitations. -2. `__execute__` executes the transaction if the validation is successful. -3. `__validate_declare__` optional entrypoint similar to `__validate__` but for transactions - meant to declare other contracts. -4. `__validate_deploy__` optional entrypoint similar to `__validate__` but meant for counterfactual deployments. - -| | | -| --- | --- | -| | Although these entrypoints are available to the protocol for its regular transaction flow, they can also be called like any other method. | - -## Starknet Account - -Starknet native account abstraction pattern allows for the creation of custom accounts with different validation schemes, but -usually most account implementations validate transactions using the Stark curve which is the most efficient way -of validating signatures since it is a STARK-friendly curve. - -OpenZeppelin Contracts for Cairo provides AccountComponent for implementing this validation scheme. - -### Usage - -Constructing an account contract requires integrating both AccountComponent and SRC5Component. The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `AccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait AccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; - - // IPublicKey - fn get_public_key() -> felt252; - fn set_public_key(new_public_key: felt252, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IPublicKeyCamel - fn getPublicKey() -> felt252; - fn setPublicKey(newPublicKey: felt252, signature: Span); -} -``` - -## Ethereum Account - -Besides the Stark-curve account, OpenZeppelin Contracts for Cairo also offers Ethereum-flavored accounts that use the secp256k1 curve for signature validation. -For this the EthAccountComponent must be used. - -### Usage - -Constructing a secp256k1 account contract also requires integrating both EthAccountComponent and SRC5Component. -The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. -Here’s an example of a basic contract: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ClassHash; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -### Interface - -This is the full interface of the `EthAccountMixinImpl` implementation: - -``` -#[starknet::interface] -pub trait EthAccountABI { - // ISRC6 - fn __execute__(calls: Array); - fn __validate__(calls: Array) -> felt252; - fn is_valid_signature(hash: felt252, signature: Array) -> felt252; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IDeclarer - fn __validate_declare__(class_hash: felt252) -> felt252; - - // IEthDeployable - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey - ) -> felt252; - - // IEthPublicKey - fn get_public_key() -> EthPublicKey; - fn set_public_key(new_public_key: EthPublicKey, signature: Span); - - // ISRC6CamelOnly - fn isValidSignature(hash: felt252, signature: Array) -> felt252; - - // IEthPublicKeyCamel - fn getPublicKey() -> EthPublicKey; - fn setPublicKey(newPublicKey: EthPublicKey, signature: Span); -} -``` - -## Deploying an account - -In Starknet there are two ways of deploying smart contracts: using the `deploy_syscall` and doing -counterfactual deployments. -The former can be easily done with the Universal Deployer Contract (UDC), a contract that -wraps and exposes the `deploy_syscall` to provide arbitrary deployments through regular contract calls. -But if you don’t have an account to invoke it, you will probably want to use the latter. - -To do counterfactual deployments, you need to implement another protocol-level entrypoint named -`__validate_deploy__`. Check the counterfactual deployments guide to learn how. - -## Sending transactions - -Let’s now explore how to send transactions through these accounts. - -### Starknet Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyAccount { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Account Mixin - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -To deploy the account variant, compile the contract and declare the class hash because custom accounts are likely not declared. -This means that you’ll need an account already deployed. - -Next, create the account JSON with Starknet Foundry’s custom account setup and include the `--class-hash` flag with the declared class hash. -The flag enables custom account variants. - -| | | -| --- | --- | -| | The following examples use `sncast` v0.23.0. | - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account create \ - --name my-custom-account \ - --class-hash 0x123456... -``` - -This command will output the precomputed contract address and the recommended `max-fee`. -To counterfactually deploy the account, send funds to the address and then deploy the custom account. - -``` -$ sncast \ - --url http://127.0.0.1:5050 \ - account deploy \ - --name my-custom-account -``` - -Once the account is deployed, set the `--account` flag with the custom account name to send transactions from that account. - -``` -$ sncast \ - --account my-custom-account \ - --url http://127.0.0.1:5050 \ - invoke \ - --contract-address 0x123... \ - --function "some_function" \ - --calldata 1 2 3 -``` - -### Ethereum Account - -First, let’s take the example account we created before and deploy it: - -``` -#[starknet::contract(account)] -mod MyEthAccount { - use openzeppelin_account::EthAccountComponent; - use openzeppelin_account::interface::EthPublicKey; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // EthAccount Mixin - #[abi(embed_v0)] - impl EthAccountMixinImpl = - EthAccountComponent::EthAccountMixinImpl; - impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - eth_account: EthAccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - EthAccountEvent: EthAccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: EthPublicKey) { - self.eth_account.initializer(public_key); - } -} -``` - -Special tooling is required in order to deploy and send transactions with an Ethereum-flavored account contract. -The following examples utilize the StarknetJS library. - -Compile and declare the contract on the target network. -Next, precompute the EthAccount contract address using the declared class hash. - -| | | -| --- | --- | -| | The following examples use unreleased features from StarknetJS (`starknetjs@next`) at commit d002baea0abc1de3ac6e87a671f3dec3757437b3. | - -``` -import * as dotenv from 'dotenv'; -import { CallData, EthSigner, hash } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Calculate EthAccount address -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountClassHash = ''; -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployerAddress = '0x0'; -const ethContractAddress = hash.calculateContractAddressFromHash( - salt, - ethAccountClassHash, - ethAccountConstructorCalldata, - deployerAddress -); -console.log('Pre-calculated EthAccount address: ', ethContractAddress); -``` - -Send funds to the pre-calculated EthAccount address and deploy the contract. - -``` -import * as dotenv from 'dotenv'; -import { Account, CallData, EthSigner, RpcProvider, stark } from 'starknet'; -import { ABI as ETH_ABI } from '../abis/eth_account.js'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethPubKey = await ethSigner.getPubKey(); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare payload -const ethAccountClassHash = '' -const ethCallData = new CallData(ETH_ABI); -const ethAccountConstructorCalldata = ethCallData.compile('constructor', { - public_key: ethPubKey -}) -const salt = '0x12345'; -const deployPayload = { - classHash: ethAccountClassHash, - constructorCalldata: ethAccountConstructorCalldata, - addressSalt: salt, -}; - -// Deploy -const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); -const { transaction_hash, contract_address } = await ethAccount.deployAccount( - deployPayload, - { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } -); -await provider.waitForTransaction(transaction_hash); -console.log('EthAccount deployed at: ', contract_address); -``` - -Once deployed, connect the EthAccount instance to the target contract which enables calls to come from the EthAccount. -Here’s what an ERC20 transfer from an EthAccount looks like. - -``` -import * as dotenv from 'dotenv'; -import { Account, RpcProvider, Contract, EthSigner } from 'starknet'; -dotenv.config(); - -// Prepare EthAccount -const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); -const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); -const ethAccountAddress = '' -const ethAccount = new Account(provider, ethAccountAddress, ethSigner); - -// Prepare target contract -const erc20 = new Contract(compiledErc20.abi, erc20Address, provider); - -// Connect EthAccount with the target contract -erc20.connect(ethAccount); - -// Execute ERC20 transfer -const transferCall = erc20.populate('transfer', { - recipient: recipient.address, - amount: 50n -}); -const tx = await erc20.transfer( - transferCall.calldata, { maxFee: 900_000_000_000_000 } -); -await provider.waitForTransaction(tx.transaction_hash); -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/backwards-compatibility - -## Backwards Compatibility - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Backwards Compatibility - -OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees. - -Bear in mind that while releasing versions, we treat minors as majors and patches as minors, in accordance with semantic versioning. This means that `v2.1.0` could be adding features to `v2.0.0`, while `v3.0.0` would be considered a breaking release. - -## API - -In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below. - -### Security - -Infrequently, a patch or minor update will remove or change an API in a breaking way but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory. - -### Errors - -The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified. - -### Major releases - -Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem. - -An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another. - -In the case of breaking "upgrade compatibility", an entry to the changelog will be added listing those breaking changes. - -## Storage layout - -Patch updates will always preserve storage layout compatibility, and after `v2.0.0-alpha.0` minors will too. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent. - -## Cairo version - -The minimum Cairo version required to compile the contracts will remain unchanged for patch updates, but it may change for minors. - -← Test Utilities - -Contracts for Solidity → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/components - -## Components - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Components - -The following documentation provides reasoning and examples on how to use Contracts for Cairo components. - -Starknet components are separate modules that contain storage, events, and implementations that can be integrated into a contract. -Components themselves cannot be declared or deployed. -Another way to think of components is that they are abstract modules that must be instantiated. - -| | | -| --- | --- | -| | For more information on the construction and design of Starknet components, see the Starknet Shamans post and the Cairo book. | - -## Building a contract - -### Setup - -The contract should first import the component and declare it with the `component!` macro: - -``` -#[starknet::contract] -mod MyContract { - // Import the component - use openzeppelin_security::InitializableComponent; - - // Declare the component - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); -} -``` - -The `path` argument should be the imported component itself (in this case, InitializableComponent). -The `storage` and `event` arguments are the variable names that will be set in the `Storage` struct and `Event` enum, respectively. -Note that even if the component doesn’t define any events, the compiler will still create an empty event enum inside the component module. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -The `#[substorage(v0)]` attribute must be included for each component in the `Storage` trait. -This allows the contract to have indirect access to the component’s storage. -See Accessing component storage for more on this. - -The `#[flat]` attribute for events in the `Event` enum, however, is not required. -For component events, the first key in the event log is the component ID. -Flattening the component event removes it, leaving the event ID as the first key. - -### Implementations - -Components come with granular implementations of different interfaces. -This allows contracts to integrate only the implementations that they’ll use and avoid unnecessary bloat. -Integrating an implementation looks like this: - -``` -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - // Gives the contract access to the implementation methods - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -Defining an `impl` gives the contract access to the methods within the implementation from the component. -For example, `is_initialized` is defined in the `InitializableImpl`. -A function on the contract level can expose it like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - (...) - - impl InitializableImpl = - InitializableComponent::InitializableImpl; - - #[external(v0)] - fn is_initialized(ref self: ContractState) -> bool { - self.initializable.is_initialized() - } -} -``` - -While there’s nothing wrong with manually exposing methods like in the previous example, this process can be tedious for implementations with many methods. -Fortunately, a contract can embed implementations which will expose all of the methods of the implementation. -To embed an implementation, add the `#[abi(embed_v0)]` attribute above the `impl`: - -``` -#[starknet::contract] -mod MyContract { - (...) - - // This attribute exposes the methods of the `impl` - #[abi(embed_v0)] - impl InitializableImpl = - InitializableComponent::InitializableImpl; -} -``` - -`InitializableImpl` defines the `is_initialized` method in the component. -By adding the embed attribute, `is_initialized` becomes a contract entrypoint for `MyContract`. - -| | | -| --- | --- | -| | Embeddable implementations, when available in this library’s components, are segregated from the internal component implementation which makes it easier to safely expose. Components also separate granular implementations from mixin implementations. The API documentation design reflects these groupings. See ERC20Component as an example which includes: * **Embeddable Mixin Implementation** * **Embeddable Implementations** * **Internal Implementations** * **Events** | - -### Mixins - -Mixins are impls made of a combination of smaller, more specific impls. -While separating components into granular implementations offers flexibility, -integrating components with many implementations can appear crowded especially if the contract uses all of them. -Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. - -Compare the following code blocks to see the benefit of using a mixin when creating an account contract. - -#### Account without mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl SRC6Impl = AccountComponent::SRC6Impl; -#[abi(embed_v0)] -impl DeclarerImpl = AccountComponent::DeclarerImpl; -#[abi(embed_v0)] -impl DeployableImpl = AccountComponent::DeployableImpl; -#[abi(embed_v0)] -impl PublicKeyImpl = AccountComponent::PublicKeyImpl; -#[abi(embed_v0)] -impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; -#[abi(embed_v0)] -impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; - -#[abi(embed_v0)] -impl SRC5Impl = SRC5Component::SRC5Impl; -``` - -#### Account with mixin - -``` -component!(path: AccountComponent, storage: account, event: AccountEvent); -component!(path: SRC5Component, storage: src5, event: SRC5Event); - -#[abi(embed_v0)] -impl AccountMixinImpl = AccountComponent::AccountMixinImpl; -impl AccountInternalImpl = AccountComponent::InternalImpl; -``` - -The rest of the setup for the contract, however, does not change. -This means that component dependencies must still be included in the `Storage` struct and `Event` enum. -Here’s a full example of an account contract that embeds the `AccountMixinImpl`: - -``` -#[starknet::contract] -mod Account { - use openzeppelin_account::AccountComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccountComponent, storage: account, event: AccountEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // This embeds all of the methods from the many AccountComponent implementations - // and also includes `supports_interface` from `SRC5Impl` - #[abi(embed_v0)] - impl AccountMixinImpl = AccountComponent::AccountMixinImpl; - impl AccountInternalImpl = AccountComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - account: AccountComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccountEvent: AccountComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState, public_key: felt252) { - self.account.initializer(public_key); - } -} -``` - -### Initializers - -| | | -| --- | --- | -| | Failing to use a component’s `initializer` can result in irreparable contract deployments. Always read the API documentation for each integrated component. | - -Some components require some sort of setup upon construction. -Usually, this would be a job for a constructor; however, components themselves cannot implement constructors. -Components instead offer `initializer`s within their `InternalImpl` to call from the contract’s constructor. -Let’s look at how a contract would integrate OwnableComponent: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::ownable::OwnableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = OwnableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - // Invoke ownable's `initializer` - self.ownable.initializer(owner); - } -} -``` - -### Immutable Config - -While initializers help set up the component’s initial state, some require configuration that may be defined -as constants, saving gas by avoiding the necessity of reading from storage each time the variable needs to be used. The -Immutable Component Config pattern helps with this matter by allowing the implementing contract to define a set of -constants declared in the component, customizing its functionality. - -| | | -| --- | --- | -| | The Immutable Component Config standard is defined in the SRC-107. | - -Here’s an example of how to use the Immutable Component Config pattern with the ERC2981Component: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::common::erc2981::ERC2981Component; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc2981: ERC2981Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC2981Event: ERC2981Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - // Define the immutable config - pub impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig { - const FEE_DENOMINATOR: u128 = 10_000; - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### Default config - -Sometimes, components implementing the Immutable Component Config pattern provide a default configuration that can be -directly used without implementing the `ImmutableConfig` trait locally. When provided, this implementation will be named -`DefaultConfig` and will be available in the same module containing the component, as a sibling. - -In the following example, the `DefaultConfig` trait is used to define the `FEE_DENOMINATOR` config constant. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - // Bring the DefaultConfig trait into scope - use openzeppelin_token::common::erc2981::{ERC2981Component, DefaultConfig}; - use starknet::contract_address_const; - - component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - // Instantiate `InternalImpl` to give the contract access to the `initializer` - impl InternalImpl = ERC2981Component::InternalImpl; - - #[storage] - struct Storage { - (...) - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - (...) - } - - #[constructor] - fn constructor(ref self: ContractState) { - let default_receiver = contract_address_const::<'RECEIVER'>(); - let default_royalty_fraction = 1000; - // Invoke erc2981's `initializer` - self.erc2981.initializer(default_receiver, default_royalty_fraction); - } -} -``` - -#### `validate` function - -The `ImmutableConfig` trait may also include a `validate` function with a default implementation, which -asserts that the configuration is correct, and must not be overridden by the implementing contract. For more information -on how to use this function, refer to the validate section of the SRC-107. - -### Dependencies - -Some components include dependencies of other components. -Contracts that integrate components with dependencies must also include the component dependency. -For instance, AccessControlComponent depends on SRC5Component. -Creating a contract with `AccessControlComponent` should look like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - #[abi(embed_v0)] - impl AccessControlCamelImpl = - AccessControlComponent::AccessControlCamelImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - (...) -} -``` - -## Customization - -| | | -| --- | --- | -| | Customizing implementations and accessing component storage can potentially corrupt the state, bypass security checks, and undermine the component logic. **Exercise extreme caution**. See Security. | - -### Hooks - -Hooks are entrypoints to the business logic of a token component that are accessible at the contract level. -This allows contracts to insert additional behaviors before and/or after token transfers (including mints and burns). -Prior to hooks, extending functionality required contracts to create custom implementations. - -All token components include a generic hooks trait that include empty default functions. -When creating a token contract, the using contract must create an implementation of the hooks trait. -Suppose an ERC20 contract wanted to include Pausable functionality on token transfers. -The following snippet leverages the `before_update` hook to include this behavior. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_security::pausable::PausableComponent::InternalTrait; - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // Create the hooks implementation - impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { - // Occurs before token transfers - fn before_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - // Access local state from component state - let contract_state = self.get_contract(); - // Call function from integrated component - contract_state.pausable.assert_not_paused(); - } - - // Omitting the `after_update` hook because the default behavior - // is already implemented in the trait - } - - (...) -} -``` - -Notice that the `self` parameter expects a component state type. -Instead of passing the component state, the using contract’s state can be passed which simplifies the syntax. -The hook then moves the scope up with the Cairo-generated `get_contract` through the `HasComponent` trait (as illustrated with ERC20Component in this example). -From here, the hook can access the using contract’s integrated components, storage, and implementations. - -Be advised that even if a token contract does not require hooks, the hooks trait must still be implemented. -The using contract may instantiate an empty impl of the trait; -however, the Contracts for Cairo library already provides the instantiated impl to abstract this away from contracts. -The using contract just needs to bring the implementation into scope like this: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_token::erc20::ERC20HooksEmptyImpl; - - (...) -} -``` - -| | | -| --- | --- | -| | For a more in-depth guide on hooks, see Extending Cairo Contracts with Hooks. | - -### Custom implementations - -There are instances where a contract requires different or amended behaviors from a component implementation. -In these scenarios, a contract must create a custom implementation of the interface. -Let’s break down a pausable ERC20 contract to see what that looks like. -Here’s the setup: - -``` -#[starknet::contract] -mod ERC20Pausable { - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - // Import the ERC20 interfaces to create custom implementations - use openzeppelin_token::erc20::interface::{IERC20, IERC20CamelOnly}; - use starknet::ContractAddress; - - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - // `ERC20MetadataImpl` can keep the embed directive because the implementation - // will not change - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - // Do not add the embed directive to these implementations because - // these will be customized - impl ERC20Impl = ERC20Component::ERC20Impl; - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -The first thing to notice is that the contract imports the interfaces of the implementations that will be customized. -These will be used in the next code example. - -Next, the contract includes the ERC20Component implementations; however, `ERC20Impl` and `ERC20CamelOnlyImplt` are **not** embedded. -Instead, we want to expose our custom implementation of an interface. -The following example shows the pausable logic integrated into the ERC20 implementations: - -``` -#[starknet::contract] -mod ERC20Pausable { - (...) - - // Custom ERC20 implementation - #[abi(embed_v0)] - impl CustomERC20Impl of IERC20 { - fn transfer( - ref self: ContractState, recipient: ContractAddress, amount: u256 - ) -> bool { - // Add the custom logic - self.pausable.assert_not_paused(); - // Add the original implementation method from `IERC20Impl` - self.erc20.transfer(recipient, amount) - } - - fn total_supply(self: @ContractState) -> u256 { - // This method's behavior does not change from the component - // implementation, but this method must still be defined. - // Simply add the original implementation method from `IERC20Impl` - self.erc20.total_supply() - } - - (...) - } - - // Custom ERC20CamelOnly implementation - #[abi(embed_v0)] - impl CustomERC20CamelOnlyImpl of IERC20CamelOnly { - fn totalSupply(self: @ContractState) -> u256 { - self.erc20.total_supply() - } - - fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { - self.erc20.balance_of(account) - } - - fn transferFrom( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) -> bool { - self.pausable.assert_not_paused(); - self.erc20.transfer_from(sender, recipient, amount) - } - } -} -``` - -Notice that in the `CustomERC20Impl`, the `transfer` method integrates `pausable.assert_not_paused` as well as `erc20.transfer` from `PausableImpl` and `ERC20Impl` respectively. -This is why the contract defined the `ERC20Impl` from the component in the previous example. - -Creating a custom implementation of an interface must define **all** methods from that interface. -This is true even if the behavior of a method does not change from the component implementation (as `total_supply` exemplifies in this example). - -### Accessing component storage - -There may be cases where the contract must read or write to an integrated component’s storage. -To do so, use the same syntax as calling an implementation method except replace the name of the method with the storage variable like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - (...) - - fn write_to_comp_storage(ref self: ContractState) { - self.initializable.Initializable_initialized.write(true); - } - - fn read_from_comp_storage(self: @ContractState) -> bool { - self.initializable.Initializable_initialized.read() - } -} -``` - -## Security - -The maintainers of OpenZeppelin Contracts for Cairo are mainly concerned with the correctness and security of the code as published in the library. - -Customizing implementations and manipulating the component state may break some important assumptions and introduce vulnerabilities. -While we try to ensure the components remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. -Any and all customizations to the component logic should be carefully reviewed and checked against the source code of the component they are customizing so as to fully understand their impact and guarantee their security. - -← Wizard - -Presets → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/erc1155 - -## ERC1155 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC1155 - -The ERC1155 multi token standard is a specification for fungibility-agnostic token contracts. -The ERC1155 library implements an approximation of EIP-1155 in Cairo for StarkNet. - -## Multi Token Standard - -The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This -is why its balance\_of function differs from ERC20’s and ERC777’s: it has an additional ID argument for the -identifier of the token that you want to query the balance of. - -This is similar to how ERC721 does things, but in that standard a token ID has no concept of balance: each token is -non-fungible and exists or doesn’t. The ERC721 balance\_of function refers to how many different tokens an account -has, not how many of each. On the other hand, in ERC1155 accounts have a distinct balance for each token ID, and -non-fungible tokens are implemented by simply minting a single one of them. - -This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new -contract for each token type, a single ERC1155 token contract can hold the entire system state, reducing deployment -costs and complexity. - -## Usage - -Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s URI and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyERC1155 { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155 Mixin - #[abi(embed_v0)] - impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; - impl ERC1155InternalImpl = ERC1155Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155: ERC1155Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155Event: ERC1155Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - token_uri: ByteArray, - recipient: ContractAddress, - token_ids: Span, - values: Span - ) { - self.erc1155.initializer(token_uri); - self - .erc1155 - .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC1155Component. -The interface includes the IERC1155 standard interface and the optional IERC1155MetadataURI interface together with ISRC5. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC1155ABI { - // IERC1155 - fn balance_of(account: ContractAddress, token_id: u256) -> u256; - fn balance_of_batch( - accounts: Span, token_ids: Span - ) -> Span; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - value: u256, - data: Span - ); - fn safe_batch_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ); - fn is_approved_for_all( - owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn set_approval_for_all(operator: ContractAddress, approved: bool); - - // IERC1155MetadataURI - fn uri(token_id: u256) -> ByteArray; - - // ISRC5 - fn supports_interface(interface_id: felt252) -> bool; - - // IERC1155Camel - fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; - fn balanceOfBatch( - accounts: Span, tokenIds: Span - ) -> Span; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - value: u256, - data: Span - ); - fn safeBatchTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenIds: Span, - values: Span, - data: Span - ); - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - fn setApprovalForAll(operator: ContractAddress, approved: bool); -} -``` - -## ERC1155 Compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: - -* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. -* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. -* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. - -## Batch operations - -Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, balance\_of\_batch and safe\_batch\_transfer\_from, that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have safe\_transfer\_from for non-batch operations. - -In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as -batch\_mint\_with\_acceptance\_check. - -| | | -| --- | --- | -| | While safe\_transfer\_from and safe\_batch\_transfer\_from prevent loss by checking the receiver can handle the tokens, this yields execution to the receiver which can result in a reentrant call. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC1155 tokens, said contract must implement the `IERC1155Receiver` interface. -The recipient contract must also implement the SRC5 interface which supports interface introspection. - -### IERC1155Receiver - -``` -#[starknet::interface] -pub trait IERC1155Receiver { - fn on_erc1155_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - value: u256, - data: Span - ) -> felt252; - fn on_erc1155_batch_received( - operator: ContractAddress, - from: ContractAddress, - token_ids: Span, - values: Span, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC1155Receiver` interface exposes the on\_erc1155\_received and on\_erc1155\_batch\_received methods. -When safe\_transfer\_from and safe\_batch\_transfer\_from are called, they invoke the recipient contract’s `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which **must** return the IERC1155Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo ERC1155ReceiverComponent already returns the correct interface ID for safe token transfers. -To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc1155::ERC1155ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC1155Receiver Mixin - #[abi(embed_v0)] - impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl; - impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc1155_receiver: ERC1155ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc1155_receiver.initializer(); - } -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/erc20 - -## ERC20 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC20 - -The ERC20 token standard is a specification for fungible tokens, a type of token where all the units are exactly equal to each other. -`token::erc20::ERC20Component` provides an approximation of EIP-20 in Cairo for Starknet. - -| | | -| --- | --- | -| | Prior to Contracts v0.7.0, ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`. If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract **must** use a custom `decimals` implementation. See the Customizing decimals guide. | - -## Usage - -Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and instantiating the token implementation. -Here’s what that looks like: - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } -} -``` - -`MyToken` integrates both the `ERC20Impl` and `ERC20MetadataImpl` with the embed directive which marks the implementations as external in the contract. -While the `ERC20MetadataImpl` is optional, it’s generally recommended to include it because the vast majority of ERC20 tokens provide the metadata methods. -The above example also includes the `ERC20InternalImpl` instance. -This allows the contract’s constructor to initialize the contract and create an initial supply of tokens. - -| | | -| --- | --- | -| | For a more complete guide on ERC20 token mechanisms, see Creating ERC20 Supply. | - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC20Component. -The interface includes the IERC20 standard interface as well as the optional IERC20Metadata. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes an implementation of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20Camel - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -## ERC20 compatibility - -Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. -Some notable differences, however, can still be found, such as: - -* The `ByteArray` type is used to represent strings in Cairo. -* The component offers a dual interface which supports both snake\_case and camelCase methods, as opposed to just camelCase in Solidity. -* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. -* Function selectors are calculated differently between Cairo and Solidity. - -## Customizing decimals - -Cairo, like Solidity, does not support floating-point numbers. -To get around this limitation, ERC20 token contracts may offer a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed. -For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`. -An outside interface would display the token supply as `1.234`. -In the actual contract, however, the supply would still be the integer `1234`. -In other words, **the decimals field in no way changes the actual arithmetic** because all operations are still performed on integers. - -Most contracts use `18` decimals and this was even proposed to be compulsory (see the EIP discussion). - -### The static approach (SRC-107) - -The Contracts for Cairo `ERC20` component leverages SRC-107 to allow for a static and configurable number of decimals. -To use the default `18` decimals, you can use the `DefaultConfig` implementation by just importing it: - -``` -#[starknet::contract] -mod MyToken { - // Importing the DefaultConfig implementation would make decimals 18 by default. - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) -} -``` - -To customize this value, you can implement the ImmutableConfig trait locally in the contract. -The following example shows how to set the decimals to `6`: - -``` -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - // Custom implementation of the ERC20Component ImmutableConfig. - impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig { - const DECIMALS: u8 = 6; - } -} -``` - -### The storage approach - -For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate. - -| | | -| --- | --- | -| | Note that we are not using the MixinImpl or the DefaultConfig in this case, since we need to customize the IERC20Metadata implementation. | - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::interface; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - // The decimals value is stored locally - decimals: u8, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress, - ) { - // Call the internal function that writes decimals to storage - self._set_decimals(decimals); - - // Initialize ERC20 - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } - - #[abi(embed_v0)] - impl ERC20CustomMetadataImpl of interface::IERC20Metadata { - fn name(self: @ContractState) -> ByteArray { - self.erc20.ERC20_name.read() - } - - fn symbol(self: @ContractState) -> ByteArray { - self.erc20.ERC20_symbol.read() - } - - fn decimals(self: @ContractState) -> u8 { - self.decimals.read() - } - } - - #[generate_trait] - impl InternalImpl of InternalTrait { - fn _set_decimals(ref self: ContractState, decimals: u8) { - self.decimals.write(decimals); - } - } -} -``` - -This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage. -Note that the `decimals` state variable must be defined in the contract’s storage because this variable does not exist in the component offered by OpenZeppelin Contracts for Cairo. -It’s important to include a custom ERC20 metadata implementation and NOT use the Contracts for Cairo `ERC20MetadataImpl` in this specific case since the `decimals` method will always return `18`. - -← API Reference - -Creating Supply → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/erc4626 - -## ERC4626 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC4626 - -ERC4626 is an extension of ERC20 that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault. - -We provide a base component of ERC4626 which is designed to allow developers to easily re-configure the vault’s behavior, using traits and hooks, while staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. - -## Security concern: Inflation attack - -### Visualizing the vault - -In exchange for the assets deposited into an ERC4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault. - -* If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets. -* If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets. - -In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line. - -When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets. - -In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. - -### The attack - -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit less than 1 share worth of tokens, you will receive 0 shares, effectively making a donation. - -For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. - -In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%. - -Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens. - -We can clearly see that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. - -The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe. - -Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited. - -An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. - -In math that gives: - -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(0\) | \(0\) | - | -| after attacker’s deposit | \(a\_0\) | \(a\_0\) | \(1\) | -| after attacker’s donation | \(a\_0+a\_1\) | \(a\_0\) | \(\frac{a\_0}{a\_0+a\_1}\) | - -This means a deposit of \(u\) will give \(\frac{u \times a\_0}{a\_0 + a\_1}\) shares. - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[\frac{u \times a\_0}{a\_0+a\_1} < 1 \iff u < 1 + \frac{a\_1}{a\_0}\] - -Using \(a\_0 = 1\) and \(a\_1 = u\) is enough. So the attacker only needs \(u+1\) assets to perform a successful attack. - -It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user’s deposit. In order to target \(\frac{u}{n}\), the user needs to suffer rounding of a similar fraction, which means the user must receive at most \(n\) shares. This results in: - -\[\frac{u \times a\_0}{a\_0+a\_1} < n \iff \frac{u}{n} < 1 + \frac{a\_1}{a\_0}\] - -In this scenario, the attack is \(n\) times less powerful (in how much it is stealing) and costs \(n\) times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings. - -### Defending with a virtual offset - -The defense we propose is based on the approach used in YieldBox. It consists of two parts: - -* Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets. -* Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty. - -These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable to perform an attack. - -Following the previous math definitions, we have: - -* \(\delta\) the vault offset -* \(a\_0\) the attacker deposit -* \(a\_1\) the attacker donation -* \(u\) the user deposit - -| | Assets | Shares | Rate | -| --- | --- | --- | --- | -| initial | \(1\) | \(10^\delta\) | \(10^\delta\) | -| after attacker’s deposit | \(1+a\_0\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta\) | -| after attacker’s donation | \(1+a\_0+a\_1\) | \(10^\delta \times (1+a\_0)\) | \(10^\delta \times \frac{1+a\_0}{1+a\_0+a\_1}\) | - -One important thing to note is that the attacker only owns a fraction \(\frac{a\_0}{1 + a\_0}\) of the shares, so when doing the donation, he will only be able to recover that fraction \(\frac{a\_1 \times a\_0}{1 + a\_0}\) of the donation. The remaining \(\frac{a\_1}{1+a\_0}\) are captured by the vault. - -\[\mathit{loss} = \frac{a\_1}{1+a\_0}\] - -When the user deposits \(u\), he receives - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1}\] - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -\[10^\delta \times u \times \frac{1+a\_0}{1+a\_0+a\_1} < 1\] - -\[\iff 10^\delta \times u < \frac{1+a\_0+a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u < 1 + \frac{a\_1}{1+a\_0}\] - -\[\iff 10^\delta \times u \le \mathit{loss}\] - -* If the offset is 0, the attacker loss is at least equal to the user’s deposit. -* If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user. - -This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful. - -The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively. - -\(\delta = 3\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -\(\delta = 3\), \(a\_0 = 100\), \(a\_1 = 10^5\) - -\(\delta = 6\), \(a\_0 = 1\), \(a\_1 = 10^5\) - -## Usage - -### Custom behavior: Adding fees to the vault - -In ERC4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. -In both cases, it is essential to remain compliant with the ERC4626 requirements in regard to the preview functions. - -For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `preview_deposit(100)`. -Similarly, `preview_mint` should account for the fees that the user will have to pay on top of share’s cost. - -As for the `Deposit` event, while this is less clear in the EIP spec itself, -there seems to be consensus that it should include the number of assets paid for by the user, including the fees. - -On the other hand, when withdrawing assets, the number given by the user should correspond to what the user receives. -Any fees should be added to the quote (in shares) performed by `preview_withdraw`. - -The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted). - -The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. -The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault. - -The following example describes how fees proportional to the deposited/withdrawn amount can be implemented: - -``` -/// The mock contract charges fees in terms of assets, not shares. -/// This means that the fees are calculated based on the amount of assets that are being deposited -/// or withdrawn, and not based on the amount of shares that are being minted or redeemed. -/// This is an opinionated design decision for the purpose of testing. -/// DO NOT USE IN PRODUCTION -#[starknet::contract] -pub mod ERC4626Fees { - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::FeeConfigTrait; - use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::InternalTrait as ERC4626InternalTrait; - use openzeppelin_token::erc20::extensions::erc4626::{DefaultConfig, ERC4626DefaultLimits}; - use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::math; - use openzeppelin_utils::math::Rounding; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: ERC4626Component, storage: erc4626, event: ERC4626Event); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC4626 - #[abi(embed_v0)] - impl ERC4626ComponentImpl = ERC4626Component::ERC4626Impl; - // ERC4626MetadataImpl is a custom impl of IERC20Metadata - #[abi(embed_v0)] - impl ERC4626MetadataImpl = ERC4626Component::ERC4626MetadataImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; - - impl ERC4626InternalImpl = ERC4626Component::InternalImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc4626: ERC4626Component::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - pub entry_fee_basis_point_value: u256, - pub entry_fee_recipient: ContractAddress, - pub exit_fee_basis_point_value: u256, - pub exit_fee_recipient: ContractAddress, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC4626Event: ERC4626Component::Event, - #[flat] - ERC20Event: ERC20Component::Event, - } - - const _BASIS_POINT_SCALE: u256 = 10_000; - - /// Hooks - impl ERC4626HooksEmptyImpl of ERC4626Component::ERC4626HooksTrait { - fn after_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let entry_basis_points = contract_state.entry_fee_basis_point_value.read(); - let fee = contract_state.fee_on_total(assets, entry_basis_points); - let recipient = contract_state.entry_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - - fn before_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, - ) { - let mut contract_state = self.get_contract_mut(); - let exit_basis_points = contract_state.exit_fee_basis_point_value.read(); - let fee = contract_state.fee_on_raw(assets, exit_basis_points); - let recipient = contract_state.exit_fee_recipient.read(); - - if fee > 0 && recipient != starknet::get_contract_address() { - contract_state.transfer_fees(recipient, fee); - } - } - } - - /// Adjust fees - impl AdjustFeesImpl of FeeConfigTrait { - fn adjust_deposit( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_deposit(assets) - } - - fn adjust_mint( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_mint(assets) - } - - fn adjust_withdraw( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.add_fee_to_withdraw(assets) - } - - fn adjust_redeem( - self: @ERC4626Component::ComponentState, assets: u256, - ) -> u256 { - let contract_state = self.get_contract(); - contract_state.remove_fee_from_redeem(assets) - } - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - underlying_asset: ContractAddress, - initial_supply: u256, - recipient: ContractAddress, - entry_fee: u256, - entry_treasury: ContractAddress, - exit_fee: u256, - exit_treasury: ContractAddress, - ) { - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - self.erc4626.initializer(underlying_asset); - - self.entry_fee_basis_point_value.write(entry_fee); - self.entry_fee_recipient.write(entry_treasury); - self.exit_fee_basis_point_value.write(exit_fee); - self.exit_fee_recipient.write(exit_treasury); - } - - #[generate_trait] - pub impl InternalImpl of InternalTrait { - fn transfer_fees(ref self: ContractState, recipient: ContractAddress, fee: u256) { - let asset_address = self.asset(); - let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; - assert(asset_dispatcher.transfer(recipient, fee), 'Fee transfer failed'); - } - - fn remove_fee_from_deposit(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_total(assets, self.entry_fee_basis_point_value.read()); - assets - fee - } - - fn add_fee_to_mint(self: @ContractState, assets: u256) -> u256 { - assets + self.fee_on_raw(assets, self.entry_fee_basis_point_value.read()) - } - - fn add_fee_to_withdraw(self: @ContractState, assets: u256) -> u256 { - let fee = self.fee_on_raw(assets, self.exit_fee_basis_point_value.read()); - assets + fee - } - - fn remove_fee_from_redeem(self: @ContractState, assets: u256) -> u256 { - assets - self.fee_on_total(assets, self.exit_fee_basis_point_value.read()) - } - - /// - /// Fee operations - /// - - /// Calculates the fees that should be added to an amount `assets` that does not already - /// include fees. - /// Used in IERC4626::mint and IERC4626::withdraw operations. - fn fee_on_raw(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, Rounding::Ceil) - } - - /// Calculates the fee part of an amount `assets` that already includes fees. - /// Used in IERC4626::deposit and IERC4626::redeem operations. - fn fee_on_total(self: @ContractState, assets: u256, fee_basis_points: u256) -> u256 { - math::u256_mul_div( - assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, Rounding::Ceil, - ) - } - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC4626Component. -The full interface includes the IERC4626, IERC20, and IERC20Metadata interfaces. -Note that implementing the IERC20Metadata interface is a requirement of IERC4626. - -``` -#[starknet::interface] -pub trait ERC4626ABI { - // IERC4626 - fn asset() -> ContractAddress; - fn total_assets() -> u256; - fn convert_to_shares(assets: u256) -> u256; - fn convert_to_assets(shares: u256) -> u256; - fn max_deposit(receiver: ContractAddress) -> u256; - fn preview_deposit(assets: u256) -> u256; - fn deposit(assets: u256, receiver: ContractAddress) -> u256; - fn max_mint(receiver: ContractAddress) -> u256; - fn preview_mint(shares: u256) -> u256; - fn mint(shares: u256, receiver: ContractAddress) -> u256; - fn max_withdraw(owner: ContractAddress) -> u256; - fn preview_withdraw(assets: u256) -> u256; - fn withdraw( - assets: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - fn max_redeem(owner: ContractAddress) -> u256; - fn preview_redeem(shares: u256) -> u256; - fn redeem( - shares: u256, receiver: ContractAddress, owner: ContractAddress, - ) -> u256; - - // IERC20 - fn total_supply() -> u256; - fn balance_of(account: ContractAddress) -> u256; - fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; - fn approve(spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn decimals() -> u8; - - // IERC20CamelOnly - fn totalSupply() -> u256; - fn balanceOf(account: ContractAddress) -> u256; - fn transferFrom( - sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/erc721 - -## ERC721 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC721 - -The ERC721 token standard is a specification for non-fungible tokens, or more colloquially: NFTs. -`token::erc721::ERC721Component` provides an approximation of EIP-721 in Cairo for Starknet. - -## Usage - -Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. -The contract should also set up the constructor to initialize the token’s name, symbol, and interface support. -Here’s an example of a basic contract: - -``` -#[starknet::contract] -mod MyNFT { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721 Mixin - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721: ERC721Component::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - recipient: ContractAddress - ) { - let name = "MyNFT"; - let symbol = "NFT"; - let base_uri = "https://api.example.com/v1/"; - let token_id = 1; - - self.erc721.initializer(name, symbol, base_uri); - self.erc721.mint(recipient, token_id); - } -} -``` - -## Interface - -The following interface represents the full ABI of the Contracts for Cairo ERC721Component. -The interface includes the IERC721 standard interface and the optional IERC721Metadata interface. - -To support older token deployments, as mentioned in Dual interfaces, the component also includes implementations of the interface written in camelCase. - -``` -#[starknet::interface] -pub trait ERC721ABI { - // IERC721 - fn balance_of(account: ContractAddress) -> u256; - fn owner_of(token_id: u256) -> ContractAddress; - fn safe_transfer_from( - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); - fn approve(to: ContractAddress, token_id: u256); - fn set_approval_for_all(operator: ContractAddress, approved: bool); - fn get_approved(token_id: u256) -> ContractAddress; - fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721Metadata - fn name() -> ByteArray; - fn symbol() -> ByteArray; - fn token_uri(token_id: u256) -> ByteArray; - - // IERC721CamelOnly - fn balanceOf(account: ContractAddress) -> u256; - fn ownerOf(tokenId: u256) -> ContractAddress; - fn safeTransferFrom( - from: ContractAddress, - to: ContractAddress, - tokenId: u256, - data: Span - ); - fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); - fn setApprovalForAll(operator: ContractAddress, approved: bool); - fn getApproved(tokenId: u256) -> ContractAddress; - fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; - - // IERC721MetadataCamelOnly - fn tokenURI(tokenId: u256) -> ByteArray; -} -``` - -## ERC721 compatibility - -Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. -This implementation does, however, include a few notable differences such as: - -* `interface_id`s are hardcoded and initialized by the constructor. - The hardcoded values derive from Starknet’s selector calculations. - See the Introspection docs. -* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. - The difference between both functions consists of accepting `data` as an argument. - `safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. - If `data` is not used, simply pass an empty array. -* ERC721 utilizes SRC5 to declare and query interface support on Starknet as opposed to Ethereum’s EIP165. - The design for `SRC5` is similar to OpenZeppelin’s ERC165Storage. -* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). - -## Token transfers - -This library includes transfer\_from and safe\_transfer\_from to transfer NFTs. -If using `transfer_from`, **the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.** -The `safe_transfer_from` method mitigates this risk by querying the recipient contract’s interface support. - -| | | -| --- | --- | -| | Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. | - -## Receiving tokens - -In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. -The recipient contract must also implement the SRC5 interface which, as described earlier, supports interface introspection. - -### IERC721Receiver - -``` -#[starknet::interface] -pub trait IERC721Receiver { - fn on_erc721_received( - operator: ContractAddress, - from: ContractAddress, - token_id: u256, - data: Span - ) -> felt252; -} -``` - -Implementing the `IERC721Receiver` interface exposes the on\_erc721\_received method. -When safe methods such as safe\_transfer\_from and safe\_mint are called, they invoke the recipient contract’s `on_erc721_received` method which **must** return the IERC721Receiver interface ID. -Otherwise, the transaction will fail. - -| | | -| --- | --- | -| | For information on how to calculate interface IDs, see Computing the interface ID. | - -### Creating a token receiver contract - -The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. -To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract’s constructor. -Here’s an example of a simple token receiver contract: - -``` -#[starknet::contract] -mod MyTokenReceiver { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721ReceiverComponent; - use starknet::ContractAddress; - - component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721Receiver Mixin - #[abi(embed_v0)] - impl ERC721ReceiverMixinImpl = ERC721ReceiverComponent::ERC721ReceiverMixinImpl; - impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721_receiver: ERC721ReceiverComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721ReceiverEvent: ERC721ReceiverComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721_receiver.initializer(); - } -} -``` - -## Storing ERC721 URIs - -Token URIs were previously stored as single field elements prior to Cairo v0.2.5. -ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the token\_uri method. -This design mirrors OpenZeppelin’s default Solidity implementation for ERC721. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/finance - -## Finance - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Finance - -This module includes primitives for financial systems. - -## Vesting component - -The VestingComponent manages the gradual release of ERC-20 tokens to a designated beneficiary based on a predefined vesting schedule. -The implementing contract must implement the OwnableComponent, where the contract owner is regarded as the vesting beneficiary. -This structure allows ownership rights of both the contract and the vested tokens to be assigned and transferred. - -| | | -| --- | --- | -| | Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning of the vesting period. As a result, if the vesting has already started, a portion of the newly transferred tokens may become immediately releasable. | - -| | | -| --- | --- | -| | By setting the duration to 0, it’s possible to configure this contract to behave like an asset timelock that holds tokens for a beneficiary until a specified date. | - -### Vesting schedule - -The VestingSchedule trait defines the logic for calculating the vested amount based on a given timestamp. This -logic is not part of the VestingComponent, so any contract implementing the VestingComponent must provide its own -implementation of the VestingSchedule trait. - -| | | -| --- | --- | -| | There’s a ready-made implementation of the VestingSchedule trait available named LinearVestingSchedule. It incorporates a cliff period by returning 0 vested amount until the cliff ends. After the cliff, the vested amount is calculated as directly proportional to the time elapsed since the beginning of the vesting schedule. | - -### Usage - -The contract must integrate VestingComponent and OwnableComponent as dependencies. The contract’s constructor -should initialize both components. Core vesting parameters, such as `beneficiary`, `start`, `duration` -and `cliff_duration`, are passed as arguments to the constructor and set at the time of deployment. - -The implementing contract must provide an implementation of the VestingSchedule trait. This can be achieved either by importing -a ready-made LinearVestingSchedule implementation or by defining a custom one. - -Here’s an example of a simple vesting wallet contract with a LinearVestingSchedule, where the vested amount -is calculated as being directly proportional to the time elapsed since the start of the vesting period. - -``` -#[starknet::contract] -mod LinearVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule}; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff_duration: u64 - ) { - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff_duration); - } -} -``` - -A vesting schedule will often follow a custom formula. In such cases, the VestingSchedule trait is useful. -To support a custom vesting schedule, the contract must provide an implementation of the -calculate\_vested\_amount function based on the desired formula. - -| | | -| --- | --- | -| | When using a custom VestingSchedule implementation, the LinearVestingSchedule must be excluded from the imports. | - -| | | -| --- | --- | -| | If there are additional parameters required for calculations, which are stored in the contract’s storage, you can access them using `self.get_contract()`. | - -Here’s an example of a vesting wallet contract with a custom VestingSchedule implementation, where tokens -are vested in a number of steps. - -``` -#[starknet::contract] -mod StepsVestingWallet { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait; - use openzeppelin_finance::vesting::VestingComponent; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: VestingComponent, storage: vesting, event: VestingEvent); - - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - #[abi(embed_v0)] - impl VestingImpl = VestingComponent::VestingImpl; - impl VestingInternalImpl = VestingComponent::InternalImpl; - - #[storage] - struct Storage { - total_steps: u64, - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - vesting: VestingComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - VestingEvent: VestingComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - total_steps: u64, - beneficiary: ContractAddress, - start: u64, - duration: u64, - cliff: u64, - ) { - self.total_steps.write(total_steps); - self.ownable.initializer(beneficiary); - self.vesting.initializer(start, duration, cliff); - } - - impl VestingSchedule of VestingScheduleTrait { - fn calculate_vested_amount( - self: @VestingComponent::ComponentState, - token: ContractAddress, - total_allocation: u256, - timestamp: u64, - start: u64, - duration: u64, - cliff: u64, - ) -> u256 { - if timestamp < cliff { - 0 - } else if timestamp >= start + duration { - total_allocation - } else { - let total_steps = self.get_contract().total_steps.read(); - let vested_per_step = total_allocation / total_steps.into(); - let step_duration = duration / total_steps; - let current_step = (timestamp - start) / step_duration; - let vested_amount = vested_per_step * current_step.into(); - vested_amount - } - } - } -} -``` - -### Interface - -Here is the full interface of a standard contract implementing the vesting functionality: - -``` -#[starknet::interface] -pub trait VestingABI { - // IVesting - fn start(self: @TState) -> u64; - fn cliff(self: @TState) -> u64; - fn duration(self: @TState) -> u64; - fn end(self: @TState) -> u64; - fn released(self: @TState, token: ContractAddress) -> u256; - fn releasable(self: @TState, token: ContractAddress) -> u256; - fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256; - fn release(ref self: TState, token: ContractAddress) -> u256; - - // IOwnable - fn owner(self: @TState) -> ContractAddress; - fn transfer_ownership(ref self: TState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TState); - - // IOwnableCamelOnly - fn transferOwnership(ref self: TState, newOwner: ContractAddress); - fn renounceOwnership(ref self: TState); -} -``` - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/governance/governor - -## Governor - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Governor - -Decentralized protocols are in constant evolution from the moment they are publicly released. Often, -the initial team retains control of this evolution in the first stages, but eventually delegates it -to a community of stakeholders. The process by which this community makes decisions is called -on-chain governance, and it has become a central component of decentralized protocols, fueling -varied decisions such as parameter tweaking, smart contract upgrades, integrations with other -protocols, treasury management, grants, etc. - -This governance protocol is generally implemented in a special-purpose contract called “Governor”. In -OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different -requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, -but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. - -## Usage and setup - -### Token - -The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has -to implement the VotesComponent extension. This extension will keep track of historical balances so that voting power -is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. - -If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a -governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. - -| | | -| --- | --- | -| | The library currently does not include a wrapper for tokens, but it will be added in a future release. | - -| | | -| --- | --- | -| | Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, block numbers instead. | - -### Governor - -We will initially build a Governor without a timelock. The core logic is given by the GovernorComponent, but we -still need to choose: - -1) how voting power is determined, - -2) how many votes are needed for quorum, - -3) what options people have when casting a vote and how those votes are counted, and - -4) the execution mechanism that should be used. - -Each of these aspects is customizable by writing your own extensions, -or more easily choosing one from the library. - -**For 1)** we will use the GovernorVotes extension, which hooks to an IVotes instance to determine the voting power -of an account based on the token balance they hold when a proposal becomes active. -This module requires the address of the token to be passed as an argument to the initializer. - -**For 2)** we will use GovernorVotesQuorumFraction. This works together with the IVotes instance to define the quorum as a -percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer -parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator -is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 4%). - -**For 3)** we will use GovernorCountingSimple, an extension that offers 3 options to voters: For, Against, and Abstain, -and where only For and Abstain votes are counted towards quorum. - -**For 4)** we will use GovernorCoreExecution, an extension that allows proposal execution directly through the governor. - -| | | -| --- | --- | -| | Another option is GovernorTimelockExecution. An example can be found in the next section. | - -Besides these, we also need an implementation for the GovernorSettingsTrait defining the voting delay, voting period, -and proposal threshold. While we can use the GovernorSettings extension which allows to set these parameters by the -governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, -and proposal threshold as constant values. - -*voting\_delay*: How long after a proposal is created should voting power be fixed. A large voting delay gives -users time to unstake tokens if necessary. - -*voting\_period*: How long does a proposal remain open to votes. - -| | | -| --- | --- | -| | These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. | - -*proposal\_threshold*: This restricts proposal creation to accounts who have enough voting power. - -An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used -the `DefaultConfig`. Check the Immutable Component Config guide for more details. - -The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. - -``` -#[starknet::contract] -mod MyGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, - GovernorCoreExecutionComponent, - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM_NUMERATOR: u256 = 40; // 4% - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!( - path: GovernorVotesQuorumFractionComponent, - storage: governor_votes, - event: GovernorVotesEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorCoreExecutionComponent, - storage: governor_core_execution, - event: GovernorCoreExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl QuorumFractionImpl = - GovernorVotesQuorumFractionComponent::QuorumFractionImpl; - - // Extensions internal - impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; - impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorCoreExecutionImpl = - GovernorCoreExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_core_execution: GovernorCoreExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, votes_token: ContractAddress) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { - /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. - fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_DELAY - } - - /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. - fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { - VOTING_PERIOD - } - - /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. - fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { - PROPOSAL_THRESHOLD - } - } -} -``` - -### Timelock - -It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree -with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the -GovernorTimelockExecution extension. - -| | | -| --- | --- | -| | When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. | - -TimelockController uses an AccessControl setup that we need to understand in order to set up roles. - -The Proposer role is in charge of queueing operations: this is the role the Governor instance must be granted, -and it MUST be the only proposer (and canceller) in the system. - -The Executor role is in charge of executing already available operations: we can assign this role to the special -zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). - -The Canceller role is in charge of canceling operations: the Governor instance must be granted this role, -and it MUST be the only canceller in the system. - -Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. - -The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a -fixed quorum value instead of a percentage: - -``` -#[starknet::contract] -pub mod MyTimelockedGovernor { - use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; - use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; - use openzeppelin_governance::governor::extensions::{ - GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, - GovernorTimelockExecutionComponent - }; - use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - pub const VOTING_DELAY: u64 = 86400; // 1 day - pub const VOTING_PERIOD: u64 = 604800; // 1 week - pub const PROPOSAL_THRESHOLD: u256 = 10; - pub const QUORUM: u256 = 100_000_000; - - component!(path: GovernorComponent, storage: governor, event: GovernorEvent); - component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); - component!( - path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent - ); - component!( - path: GovernorCountingSimpleComponent, - storage: governor_counting_simple, - event: GovernorCountingSimpleEvent - ); - component!( - path: GovernorTimelockExecutionComponent, - storage: governor_timelock_execution, - event: GovernorTimelockExecutionEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Governor - #[abi(embed_v0)] - impl GovernorImpl = GovernorComponent::GovernorImpl; - - // Extensions external - #[abi(embed_v0)] - impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; - #[abi(embed_v0)] - impl GovernorSettingsAdminImpl = - GovernorSettingsComponent::GovernorSettingsAdminImpl; - #[abi(embed_v0)] - impl TimelockedImpl = - GovernorTimelockExecutionComponent::TimelockedImpl; - - // Extensions internal - impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; - impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; - impl GovernorCountingSimpleImpl = - GovernorCountingSimpleComponent::GovernorCounting; - impl GovernorTimelockExecutionImpl = - GovernorTimelockExecutionComponent::GovernorExecution; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - pub governor: GovernorComponent::Storage, - #[substorage(v0)] - pub governor_votes: GovernorVotesComponent::Storage, - #[substorage(v0)] - pub governor_settings: GovernorSettingsComponent::Storage, - #[substorage(v0)] - pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, - #[substorage(v0)] - pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - GovernorEvent: GovernorComponent::Event, - #[flat] - GovernorVotesEvent: GovernorVotesComponent::Event, - #[flat] - GovernorSettingsEvent: GovernorSettingsComponent::Event, - #[flat] - GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, - #[flat] - GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - } - - #[constructor] - fn constructor( - ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress - ) { - self.governor.initializer(); - self.governor_votes.initializer(votes_token); - self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); - self.governor_timelock_execution.initializer(timelock_controller); - } - - // - // SNIP12 Metadata - // - - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // - // Locally implemented extensions - // - - impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { - /// See `GovernorComponent::GovernorQuorumTrait::quorum`. - fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { - QUORUM - } - } -} -``` - -## Interface - -This is the full interface of the `Governor` implementation: - -``` -#[starknet::interface] -pub trait IGovernor { - fn name(self: @TState) -> felt252; - fn version(self: @TState) -> felt252; - fn COUNTING_MODE(self: @TState) -> ByteArray; - fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; - fn state(self: @TState, proposal_id: felt252) -> ProposalState; - fn proposal_threshold(self: @TState) -> u256; - fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; - fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; - fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; - fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; - fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; - fn voting_delay(self: @TState) -> u64; - fn voting_period(self: @TState) -> u64; - fn quorum(self: @TState, timepoint: u64) -> u256; - fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_votes_with_params( - self: @TState, account: ContractAddress, timepoint: u64, params: Span - ) -> u256; - fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; - fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; - fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; - fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; - fn cast_vote_with_reason( - ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray - ) -> u256; - fn cast_vote_with_reason_and_params( - ref self: TState, - proposal_id: felt252, - support: u8, - reason: ByteArray, - params: Span - ) -> u256; - fn cast_vote_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - signature: Span - ) -> u256; - fn cast_vote_with_reason_and_params_by_sig( - ref self: TState, - proposal_id: felt252, - support: u8, - voter: ContractAddress, - reason: ByteArray, - params: Span, - signature: Span - ) -> u256; - fn nonces(self: @TState, voter: ContractAddress) -> felt252; - fn relay(ref self: TState, call: Call); -} -``` - -← API Reference - -Multisig → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/governance/multisig - -## Multisig - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Multisig - -The Multisig component implements a multi-signature mechanism to enhance the security and -governance of smart contract transactions. It ensures that no single signer can unilaterally -execute critical actions, requiring multiple registered signers to approve and collectively -execute transactions. - -This component is designed to secure operations such as fund management or protocol governance, -where collective decision-making is essential. The Multisig Component is self-administered, -meaning that changes to signers or quorum must be approved through the multisig process itself. - -## Key features - -* **Multi-Signature Security**: transactions must be approved by multiple signers, ensuring - distributed governance. -* **Quorum Enforcement**: defines the minimum number of approvals required for transaction execution. -* **Self-Administration**: all modifications to the component (e.g., adding or removing signers) - must pass through the multisig process. -* **Event Logging**: provides comprehensive event logging for transparency and auditability. - -## Signer management - -The Multisig component introduces the concept of signers and quorum: - -* **Signers**: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig - Component supports adding, removing, or replacing signers. -* **Quorum**: the quorum defines the minimum number of confirmations required to approve a transaction. - -| | | -| --- | --- | -| | To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum. This ensures that all modifications pass through the multisig approval process. | - -## Transaction lifecycle - -The state of a transaction is represented by the `TransactionState` enum and can be retrieved -by calling the `get_transaction_state` function with the transaction’s identifier. - -The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash -of the transaction’s calls and salt. It can be computed by invoking the implementing contract’s -`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call -transactions. Submitting a transaction with identical calls and the same salt value a second time -will fail, as transaction identifiers must be unique. To resolve this, use a different salt value -to generate a unique identifier. - -A transaction in the Multisig component follows a specific lifecycle: - -`NotFound` → `Pending` → `Confirmed` → `Executed` - -* **NotFound**: the transaction does not exist. -* **Pending**: the transaction exists but has not reached the required confirmations. -* **Confirmed**: the transaction has reached the quorum but has not yet been executed. -* **Executed**: the transaction has been successfully executed. - -## Usage - -Integrating the Multisig functionality into a contract requires implementing MultisigComponent. -The contract’s constructor should initialize the component with a quorum value and a list of initial signers. - -Here’s an example of a simple wallet contract featuring the Multisig functionality: - -``` -#[starknet::contract] -mod MultisigWallet { - use openzeppelin_governance::multisig::MultisigComponent; - use starknet::ContractAddress; - - component!(path: MultisigComponent, storage: multisig, event: MultisigEvent); - - #[abi(embed_v0)] - impl MultisigImpl = MultisigComponent::MultisigImpl; - impl MultisigInternalImpl = MultisigComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - multisig: MultisigComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - MultisigEvent: MultisigComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, quorum: u32, signers: Span) { - self.multisig.initializer(quorum, signers); - } -} -``` - -## Interface - -This is the interface of a contract implementing the MultisigComponent: - -``` -#[starknet::interface] -pub trait MultisigABI { - // Read functions - fn get_quorum(self: @TState) -> u32; - fn is_signer(self: @TState, signer: ContractAddress) -> bool; - fn get_signers(self: @TState) -> Span; - fn is_confirmed(self: @TState, id: TransactionID) -> bool; - fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool; - fn is_executed(self: @TState, id: TransactionID) -> bool; - fn get_submitted_block(self: @TState, id: TransactionID) -> u64; - fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState; - fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32; - fn hash_transaction( - self: @TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID; - - // Write functions - fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span); - fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span); - fn replace_signer( - ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress, - ); - fn change_quorum(ref self: TState, new_quorum: u32); - fn submit_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ) -> TransactionID; - fn submit_transaction_batch( - ref self: TState, calls: Span, salt: felt252, - ) -> TransactionID; - fn confirm_transaction(ref self: TState, id: TransactionID); - fn revoke_confirmation(ref self: TState, id: TransactionID); - fn execute_transaction( - ref self: TState, - to: ContractAddress, - selector: felt252, - calldata: Span, - salt: felt252, - ); - fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252); -} -``` - -← Governor - -Timelock Controller → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/governance/timelock - -## Timelock Controller - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Timelock Controller - -The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. - -| | | -| --- | --- | -| | The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. | - -## Operation lifecycle - -The state of an operation is represented by the `OperationState` enum and can be retrieved -by calling the `get_operation_state` function with the operation’s identifier. - -The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the -operation’s call or calls, its predecessor, and salt. It can be computed by invoking the -implementing contract’s `hash_operation` function for single-call operations or -`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls, -predecessor, and the same salt value a second time will fail, as operation identifiers must be -unique. To resolve this, use a different salt value to generate a unique identifier. - -Timelocked operations follow a specific lifecycle: - -`Unset` → `Waiting` → `Ready` → `Done` - -* `Unset`: the operation has not been scheduled or has been canceled. -* `Waiting`: the operation has been scheduled and is pending the scheduled delay. -* `Ready`: the timer has expired, and the operation is eligible for execution. -* `Done`: the operation has been executed. - -## Timelock flow - -### Schedule - -When a proposer calls schedule, the `OperationState` moves from `Unset` to `Waiting`. -This starts a timer that must be greater than or equal to the minimum delay. -The timer expires at a timestamp accessible through get\_timestamp. -Once the timer expires, the `OperationState` automatically moves to the `Ready` state. -At this point, it can be executed. - -### Execute - -By calling execute, an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor’s operation must be in the `Done` state for this transaction to succeed. - -### Cancel - -The cancel function allows cancellers to cancel any pending operations. -This resets the operation to the `Unset` state. -It is therefore possible for a proposer to re-schedule an operation that has been cancelled. -In this case, the timer restarts when the operation is re-scheduled. - -### Roles - -TimelockControllerComponent leverages an AccessControlComponent setup that we need to understand in order to set up roles. - -* `PROPOSER_ROLE` - in charge of queueing operations. -* `CANCELLER_ROLE` - may cancel scheduled operations. - During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. - Therefore, the initial proposers may also cancel operations after they are scheduled. -* `EXECUTOR_ROLE` - in charge of executing already available operations. -* `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. - -| | | -| --- | --- | -| | The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. The latter case may be required to ease a contract’s initial configuration; however, this role should promptly be renounced. | - -Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. -This allows anyone to execute an operation once it’s in the `Ready` OperationState. -To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. - -| | | -| --- | --- | -| | Be very careful with enabling open roles as *anyone* can call the function. | - -### Minimum delay - -The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. -The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. - -After initialization, the only way to change the timelock’s minimum delay is to schedule it and execute it with the same flow as any other operation. - -The minimum delay of a contract is accessible through get\_min\_delay. - -### Usage - -Integrating the timelock into a contract requires integrating TimelockControllerComponent as well as SRC5Component and AccessControlComponent as dependencies. -The contract’s constructor should initialize the timelock which consists of setting the: - -* Proposers and executors. -* Minimum delay between scheduling and executing an operation. -* Optional admin if additional configuration is required. - -| | | -| --- | --- | -| | The optional admin should renounce their role once configuration is complete. | - -Here’s an example of a simple timelock contract: - -``` -#[starknet::contract] -mod TimelockControllerContract { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_governance::timelock::TimelockControllerComponent; - use openzeppelin_introspection::src5::SRC5Component; - use starknet::ContractAddress; - - component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); - component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // Timelock Mixin - #[abi(embed_v0)] - impl TimelockMixinImpl = - TimelockControllerComponent::TimelockMixinImpl; - impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - access_control: AccessControlComponent::Storage, - #[substorage(v0)] - timelock: TimelockControllerComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - TimelockEvent: TimelockControllerComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - min_delay: u64, - proposers: Span, - executors: Span, - admin: ContractAddress - ) { - self.timelock.initializer(min_delay, proposers, executors, admin); - } -} -``` - -### Interface - -This is the full interface of the TimelockMixinImpl implementation: - -``` -#[starknet::interface] -pub trait TimelockABI { - // ITimelock - fn is_operation(self: @TState, id: felt252) -> bool; - fn is_operation_pending(self: @TState, id: felt252) -> bool; - fn is_operation_ready(self: @TState, id: felt252) -> bool; - fn is_operation_done(self: @TState, id: felt252) -> bool; - fn get_timestamp(self: @TState, id: felt252) -> u64; - fn get_operation_state(self: @TState, id: felt252) -> OperationState; - fn get_min_delay(self: @TState) -> u64; - fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; - fn hash_operation_batch( - self: @TState, calls: Span, predecessor: felt252, salt: felt252 - ) -> felt252; - fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); - fn schedule_batch( - ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 - ); - fn cancel(ref self: TState, id: felt252); - fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); - fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); - fn update_delay(ref self: TState, new_delay: u64); - - // ISRC5 - fn supports_interface(self: @TState, interface_id: felt252) -> bool; - - // IAccessControl - fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn get_role_admin(self: @TState, role: felt252) -> felt252; - fn grant_role(ref self: TState, role: felt252, account: ContractAddress); - fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); - fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); - - // IAccessControlCamel - fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; - fn getRoleAdmin(self: @TState, role: felt252) -> felt252; - fn grantRole(ref self: TState, role: felt252, account: ContractAddress); - fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); - fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); -} -``` - -← Multisig - -Votes → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/governance/votes - -## Votes - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Votes - -The VotesComponent provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. - -| | | -| --- | --- | -| | By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. | - -| | | -| --- | --- | -| | The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the usage section for examples of how to implement this. | - -## Key features - -1. **Delegation**: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the delegate function directly, or by providing a signature to be used with delegate\_by\_sig. -2. **Historical lookups**: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp. - This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. - -## Usage - -When integrating the `VotesComponent`, the VotingUnitsTrait must be implemented to get the voting units for a given account as a function of the implementing contract. -For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated. -Additionally, you must implement the NoncesComponent and the SNIP12Metadata trait to enable delegation by signatures. - -Here’s an example of how to structure a simple ERC20Votes contract: - -``` -#[starknet::contract] -mod ERC20VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC20 - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc20_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc20: ERC20Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20VotesEvent: VotesComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - // Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. - impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { - fn after_update( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let mut contract_state = self.get_contract_mut(); - contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc20.initializer("MyToken", "MTK"); - } -} -``` - -And here’s an example of how to structure a simple ERC721Votes contract: - -``` -#[starknet::contract] -pub mod ERC721VotesContract { - use openzeppelin_governance::votes::VotesComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_token::erc721::ERC721Component; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; - use starknet::ContractAddress; - - component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - // Votes - #[abi(embed_v0)] - impl VotesImpl = VotesComponent::VotesImpl; - impl VotesInternalImpl = VotesComponent::InternalImpl; - - // ERC721 - #[abi(embed_v0)] - impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - // Nonces - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - - #[storage] - pub struct Storage { - #[substorage(v0)] - pub erc721_votes: VotesComponent::Storage, - #[substorage(v0)] - pub erc721: ERC721Component::Storage, - #[substorage(v0)] - pub src5: SRC5Component::Storage, - #[substorage(v0)] - pub nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721VotesEvent: VotesComponent::Event, - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - /// Required for hash computation. - pub impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'DAPP_VERSION' - } - } - - // We need to call the `transfer_voting_units` function after - // every mint, burn and transfer. - // For this, we use the `before_update` hook of the - //`ERC721Component::ERC721HooksTrait`. - // This hook is called before the transfer is executed. - // This gives us access to the previous owner. - impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { - fn before_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) { - let mut contract_state = self.get_contract_mut(); - - // We use the internal function here since it does not check if the token - // id exists which is necessary for mints - let previous_owner = self._owner_of(token_id); - contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); - } - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.erc721.initializer("MyToken", "MTK", ""); - } -} -``` - -## Interface - -This is the full interface of the `VotesImpl` implementation: - -``` -#[starknet::interface] -pub trait VotesABI { - // IVotes - fn get_votes(self: @TState, account: ContractAddress) -> u256; - fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; - fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; - fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; - fn delegate(ref self: TState, delegatee: ContractAddress); - fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); - - // INonces - fn nonces(self: @TState, owner: ContractAddress) -> felt252; -} -``` - -← Timelock Controller - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/guides/deployment - -## Counterfactual deployments - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Counterfactual deployments - -A counterfactual contract is a contract we can interact with even before actually deploying it on-chain. -For example, we can send funds or assign privileges to a contract that doesn’t yet exist. -Why? Because deployments in Starknet are deterministic, allowing us to predict the address where our contract will be deployed. -We can leverage this property to make a contract pay for its own deployment by simply sending funds in advance. We call this a counterfactual deployment. - -This process can be described with the following steps: - -| | | -| --- | --- | -| | For testing this flow you can check the Starknet Foundry or the Starkli guides for deploying accounts. | - -1. Deterministically precompute the `contract_address` given a `class_hash`, `salt`, and constructor `calldata`. - Note that the `class_hash` must be previously declared for the deployment to succeed. -2. Send funds to the `contract_address`. Usually you will estimate the fee of the transaction first. Existing - tools usually do this for you. -3. Send a `DeployAccount` type transaction to the network. -4. The protocol will then validate the transaction with the `__validate_deploy__` entrypoint of the contract to be deployed. -5. If the validation succeeds, the protocol will charge the fee and then register the contract as deployed. - -| | | -| --- | --- | -| | Although this method is very popular to deploy accounts, this works for any kind of contract. | - -## Deployment validation - -To be counterfactually deployed, the deploying contract must implement the `__validate_deploy__` entrypoint, -called by the protocol when a `DeployAccount` transaction is sent to the network. - -``` -trait IDeployable { - /// Must return 'VALID' when the validation is successful. - fn __validate_deploy__( - class_hash: felt252, contract_address_salt: felt252, public_key: felt252 - ) -> felt252; -} -``` - -← Interfaces and Dispatchers - -SNIP12 and Typed Messages → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/guides/erc20-permit - -## ERC20Permit - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# ERC20Permit - -The EIP-2612 standard, commonly referred to as ERC20Permit, is designed to support gasless token approvals. This is achieved with an off-chain -signature following the SNIP12 standard, rather than with an on-chain transaction. The permit function verifies the signature and sets -the spender’s allowance if the signature is valid. This approach improves user experience and reduces gas costs. - -## Differences from Solidity - -Although this extension is mostly similar to the Solidity implementation of EIP-2612, there are some notable differences in the parameters of the permit function: - -* The `deadline` parameter is represented by `u64` rather than `u256`. -* The `signature` parameter is represented by a span of felts rather than `v`, `r`, and `s` values. - -| | | -| --- | --- | -| | Unlike Solidity, there is no enforced format for signatures on Starknet. A signature is represented by an array or span of felts, and there is no universal method for validating signatures of unknown formats. Consequently, a signature provided to the permit function is validated through an external `is_valid_signature` call to the contract at the `owner` address. | - -## Usage - -The functionality is provided as an embeddable ERC20Permit trait of the ERC20Component. - -``` -#[abi(embed_v0)] -impl ERC20PermitImpl = ERC20Component::ERC20PermitImpl; -``` - -A contract must meet the following requirements to be able to use the ERC20Permit trait: - -* Implement ERC20Component. -* Implement NoncesComponent. -* Implement SNIP12Metadata trait (used in signature generation). - -## Typed message - -To safeguard against replay attacks and ensure the uniqueness of each approval via permit, the data signed includes: - -* The address of the `owner`. -* The parameters specified in the approve function (`spender` and `amount`) -* The address of the `token` contract itself. -* A `nonce`, which must be unique for each operation. -* The `chain_id`, which protects against cross-chain replay attacks. - -The format of the `Permit` structure in a signed permit message is as follows: - -``` -struct Permit { - token: ContractAddress, - spender: ContractAddress, - amount: u256, - nonce: felt252, - deadline: u64, -} -``` - -| | | -| --- | --- | -| | The owner of the tokens is also part of the signed message. It is used as the `signer` parameter in the `get_message_hash` call. | - -Further details on preparing and signing a typed message can be found in the SNIP12 guide. - -← Creating Supply - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/guides/erc20-supply - -## Creating ERC20 Supply - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Creating ERC20 Supply - -The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. -EIP20, from which ERC20 contracts are derived, does not specify how tokens are created. -This guide will go over strategies for creating both a fixed and dynamic token supply. - -## Fixed Supply - -Let’s say we want to create a token named `MyToken` with a fixed token supply. -We can achieve this by setting the token supply in the constructor which will execute upon deployment. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - fixed_supply: u256, - recipient: ContractAddress - ) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } -} -``` - -In the constructor, we’re first calling the ERC20 initializer to set the token name and symbol. -Next, we’re calling the internal `mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. -Since the internal `mint` is not exposed in our contract, it will not be possible to create any more tokens. -In other words, we’ve implemented a fixed token supply! - -## Dynamic Supply - -ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. -Let’s make a few changes to the almighty `MyToken` contract and create a minting mechanism. - -``` -#[starknet::contract] -mod MyToken { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - let name = "MyToken"; - let symbol = "MTK"; - - self.erc20.initializer(name, symbol); - } - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // This function is NOT protected which means - // ANYONE can mint tokens - self.erc20.mint(recipient, amount); - } -} -``` - -The exposed `mint` above will create `amount` tokens and allocate them to `recipient`. -We now have our minting mechanism! - -There is, however, a big problem. -`mint` does not include any restrictions on who can call this function. -For the sake of good practices, let’s implement a simple permissioning mechanism with `Ownable`. - -``` -#[starknet::contract] -mod MyToken { - - (...) - - // Integrate Ownable - - #[external(v0)] - fn mint( - ref self: ContractState, - recipient: ContractAddress, - amount: u256 - ) { - // Set permissions with Ownable - self.ownable.assert_only_owner(); - - // Mint tokens if called by the contract owner - self.erc20.mint(recipient, amount); - } -} -``` - -In the constructor, we pass the owner address to set the owner of the `MyToken` contract. -The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function. -Now, we have a protected ERC20 minting mechanism to create a dynamic token supply. - -| | | -| --- | --- | -| | For a more thorough explanation of permission mechanisms, see Access Control. | - -← ERC20 - -ERC20Permit → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/guides/snip12 - -## SNIP12 and Typed Messages - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# SNIP12 and Typed Messages - -Similar to EIP712, SNIP12 is a standard for secure off-chain signature verification on Starknet. -It provides a way to hash and sign generic typed structs rather than just strings. When building decentralized -applications, usually you might need to sign a message with complex data. The purpose of signature verification -is then to ensure that the received message was indeed signed by the expected signer, and it hasn’t been tampered with. - -OpenZeppelin Contracts for Cairo provides a set of utilities to make the implementation of this standard -as easy as possible, and in this guide we will walk you through the process of generating the hashes of typed messages -using these utilities for on-chain signature verification. For that, let’s build an example with a custom ERC20 contract -adding an extra `transfer_with_signature` method. - -| | | -| --- | --- | -| | This is an educational example, and it is not intended to be used in production environments. | - -## CustomERC20 - -Let’s start with a basic ERC20 contract leveraging the ERC20Component, and let’s add the new function. -Note that some declarations are omitted for brevity. The full example will be available at the end of the guide. - -``` -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - (...) - - #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - (...) - } -} -``` - -The `transfer_with_signature` function will allow a user to transfer tokens to another account by providing a signature. -The signature will be generated off-chain, and it will be used to verify the message on-chain. Note that the message -we need to verify is a struct with the following fields: - -* `recipient`: The address of the recipient. -* `amount`: The amount of tokens to transfer. -* `nonce`: A unique number to prevent replay attacks. -* `expiry`: The timestamp when the signature expires. - -Note that generating the hash of this message on-chain is a requirement to verify the signature, because if we accept -the message as a parameter, it could be easily tampered with. - -## Generating the Typed Message Hash - -To generate the hash of the message, we need to follow these steps: - -### 1. Define the message struct. - -In this particular example, the message struct looks like this: - -``` -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} -``` - -### 2. Get the message type hash. - -This is the `starknet_keccak(encode_type(message))` as defined in the SNIP. - -In this case it can be computed as follows: - -``` -// Since there's no u64 type in SNIP-12, we use u128 for `expiry` in the type hash generation. -let message_type_hash = selector!( - "\"Message\"(\"recipient\":\"ContractAddress\",\"amount\":\"u256\",\"nonce\":\"felt\",\"expiry\":\"u128\")\"u256\"(\"low\":\"u128\",\"high\":\"u128\")" -); -``` - -which is the same as: - -``` -let message_type_hash = 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; -``` - -| | | -| --- | --- | -| | In practice it’s better to compute the type hash off-chain and hardcode it in the contract, since it is a constant value. | - -### 3. Implement the `StructHash` trait for the struct. - -You can import the trait from: `openzeppelin_utils::snip12::StructHash`. And this implementation -is nothing more than the encoding of the message as defined in the SNIP. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::StructHash; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} -``` - -### 4. Implement the `SNIP12Metadata` trait. - -This implementation determines the values of the domain separator. Only the `name` and `version` fields are required -because the `chain_id` is obtained on-chain, and the `revision` is hardcoded to `1`. - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { 'DAPP_NAME' } - fn version() -> felt252 { 'v1' } -} -``` - -In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in -some cases we may need to read from storage to get the domain separator values. This can be accomplished even when -the trait is not bounded to the ContractState, like this: - -``` -use openzeppelin_utils::snip12::SNIP12Metadata; - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - let state = unsafe_new_contract_state(); - - // Some logic to get the name from storage - state.erc20.name().at(0).unwrap().into() - } - - fn version() -> felt252 { 'v1' } -} -``` - -### 5. Generate the hash. - -The final step is to use the `OffchainMessageHashImpl` implementation to generate the hash of the message -using the `get_message_hash` function. The implementation is already available as a utility. - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'DAPP_NAME' - } - fn version() -> felt252 { - 'v1' - } -} - -fn get_hash( - account: ContractAddress, recipient: ContractAddress, amount: u256, nonce: felt252, expiry: u64 -) -> felt252 { - let message = Message { recipient, amount, nonce, expiry }; - message.get_message_hash(account) -} -``` - -| | | -| --- | --- | -| | The expected parameter for the `get_message_hash` function is the address of account that signed the message. | - -## Full Implementation - -Finally, the full implementation of the `CustomERC20` contract looks like this: - -| | | -| --- | --- | -| | We are using the `ISRC6Dispatcher` to verify the signature, and the `NoncesComponent` to handle nonces to prevent replay attacks. | - -``` -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; -use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; -use starknet::ContractAddress; - -const MESSAGE_TYPE_HASH: felt252 = - 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; - -#[derive(Copy, Drop, Hash)] -struct Message { - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64 -} - -impl StructHashImpl of StructHash { - fn hash_struct(self: @Message) -> felt252 { - let hash_state = PoseidonTrait::new(); - hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() - } -} - -#[starknet::contract] -mod CustomERC20 { - use openzeppelin_account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_utils::cryptography::nonces::NoncesComponent; - use starknet::ContractAddress; - - use super::{Message, OffchainMessageHash, SNIP12Metadata}; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); - - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[abi(embed_v0)] - impl NoncesImpl = NoncesComponent::NoncesImpl; - impl NoncesInternalImpl = NoncesComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - nonces: NoncesComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - NoncesEvent: NoncesComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { - self.erc20.initializer("MyToken", "MTK"); - self.erc20.mint(recipient, initial_supply); - } - - /// Required for hash computation. - impl SNIP12MetadataImpl of SNIP12Metadata { - fn name() -> felt252 { - 'CustomERC20' - } - fn version() -> felt252 { - 'v1' - } - } - - #[external(v0)] - fn transfer_with_signature( - ref self: ContractState, - recipient: ContractAddress, - amount: u256, - nonce: felt252, - expiry: u64, - signature: Array - ) { - assert(starknet::get_block_timestamp() <= expiry, 'Expired signature'); - let owner = starknet::get_caller_address(); - - // Check and increase nonce - self.nonces.use_checked_nonce(owner, nonce); - - // Build hash for calling `is_valid_signature` - let message = Message { recipient, amount, nonce, expiry }; - let hash = message.get_message_hash(owner); - - let is_valid_signature_felt = ISRC6Dispatcher { contract_address: owner } - .is_valid_signature(hash, signature); - - // Check either 'VALID' or true for backwards compatibility - let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED - || is_valid_signature_felt == 1; - assert(is_valid_signature, 'Invalid signature'); - - // Transfer tokens - self.erc20._transfer(owner, recipient, amount); - } -} -``` - -← Counterfactual Deployments - -Access → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/guides/src5-migration - -## Migrating ERC165 to SRC5 - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Migrating ERC165 to SRC5 - -In the smart contract ecosystem, having the ability to query if a contract supports a given interface is an extremely important feature. -The initial introspection design for Contracts for Cairo before version v0.7.0 followed Ethereum’s EIP-165. -Since the Cairo language evolved introducing native types, we needed an introspection solution tailored to the Cairo ecosystem: the SNIP-5 standard. -SNIP-5 allows interface ID calculations to use Cairo types and the Starknet keccak (`sn_keccak`) function. -For more information on the decision, see the Starknet Shamans proposal or the Dual Introspection Detection discussion. - -## How to migrate - -Migrating from ERC165 to SRC5 involves four major steps: - -1. Integrate SRC5 into the contract. -2. Register SRC5 IDs. -3. Add a `migrate` function to apply introspection changes. -4. Upgrade the contract and call `migrate`. - -The following guide will go through the steps with examples. - -### Component integration - -The first step is to integrate the necessary components into the new contract. -The contract should include the new introspection mechanism, SRC5Component. -It should also include the InitializableComponent which will be used in the `migrate` function. -Here’s the setup: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_security::initializable::InitializableComponent; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - // Initializable - impl InitializableInternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - initializable: InitializableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - InitializableEvent: InitializableComponent::Event - } -} -``` - -### Interface registration - -To successfully migrate ERC165 to SRC5, the contract needs to register the interface IDs that the contract supports with SRC5. - -For this example, let’s say that this contract supports the IERC721 and IERC721Metadata interfaces. -The contract should implement an `InternalImpl` and add a function to register those interfaces like this: - -``` -#[starknet::contract] -mod MigratingContract { - use openzeppelin_token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID}; - - (...) - - #[generate_trait] - impl InternalImpl of InternalTrait { - // Register SRC5 interfaces - fn register_src5_interfaces(ref self: ContractState) { - self.src5.register_interface(IERC721_ID); - self.src5.register_interface(IERC721_METADATA_ID); - } - } -} -``` - -Since the new contract integrates `SRC5Component`, it can leverage SRC5’s register\_interface function to register the supported interfaces. - -### Migration initializer - -Next, the contract should define and expose a migration function that will invoke the `register_src5_interfaces` function. -Since the `migrate` function will be publicly callable, it should include some sort of Access Control so that only permitted addresses can execute the migration. -Finally, `migrate` should include a reinitialization check to ensure that it cannot be called more than once. - -| | | -| --- | --- | -| | If the original contract implemented `Initializable` at any point and called the `initialize` method, the `InitializableComponent` will not be usable at this time. Instead, the contract can take inspiration from `InitializableComponent` and create its own initialization mechanism. | - -``` -#[starknet::contract] -mod MigratingContract { - (...) - - #[external(v0)] - fn migrate(ref self: ContractState) { - // WARNING: Missing Access Control mechanism. Make sure to add one - - // WARNING: If the contract ever implemented `Initializable` in the past, - // this will not work. Make sure to create a new initialization mechanism - self.initializable.initialize(); - - // Register SRC5 interfaces - self.register_src5_interfaces(); - } -} -``` - -### Execute migration - -Once the new contract is prepared for migration and **rigorously tested**, all that’s left is to migrate! -Simply upgrade the contract and then call `migrate`. - -← Introspection - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/interfaces - -## Interfaces and Dispatchers - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Interfaces and Dispatchers - -This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. - -Interfaces can be found in the module tree under the `interface` submodule, such as `token::erc20::interface`. For example: - -``` -use openzeppelin_token::erc20::interface::IERC20; -``` - -or - -``` -use openzeppelin_token::erc20::interface::ERC20ABI; -``` - -| | | -| --- | --- | -| | For simplicity, we’ll use ERC20 as example but the same concepts apply to other modules. | - -## Interface traits - -The library offers three types of traits to implement or interact with contracts: - -### Standard traits - -These are associated with a predefined interface such as a standard. -This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. - -``` -#[starknet::interface] -pub trait IERC20 { - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### ABI traits - -They describe a contract’s complete interface. This is useful to interface with a preset contract offered by this library, such as the ERC20 preset that includes functions from different traits such as `IERC20` and `IERC20Camel`. - -| | | -| --- | --- | -| | The library offers an ABI trait for most components, providing all external function signatures even when most of the time all of them don’t need to be implemented at the same time. This can be helpful when interacting with a contract implementing the component, instead of defining a new dispatcher. | - -``` -#[starknet::interface] -pub trait ERC20ABI { - // IERC20 - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; - - // IERC20Metadata - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - - // IERC20CamelOnly - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; -} -``` - -### Dispatcher traits - -Traits annotated with `#[starknet::interface]` automatically generate a dispatcher that can be used to interact with contracts that implement the given interface. They can be imported by appending the `Dispatcher` and `DispatcherTrait` suffixes to the trait name, like this: - -``` -use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -``` - -Other types of dispatchers are also auto-generated from the annotated trait. See the -Interacting with another contract section of the Cairo book for more information. - -| | | -| --- | --- | -| | In the example, the `IERC20Dispatcher` is the one used to interact with contracts, but the `IERC20DispatcherTrait` needs to be in scope for the functions to be available. | - -## Dual interfaces - -| | | -| --- | --- | -| | `camelCase` functions are deprecated and maintained only for Backwards Compatibility. It’s recommended to only use `snake_case` interfaces with contracts and components. The `camelCase` functions will be removed in future versions. | - -Following the Great Interface Migration plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. - -In short, the library offers two types of interfaces and utilities to handle them: - -1. `camelCase` interfaces, which are the ones we’ve been using so far. -2. `snake_case` interfaces, which are the ones we’re migrating to. - -This means that currently most of our contracts implement *dual interfaces*. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. - -| | | -| --- | --- | -| | Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo (v0.6.1 and below). | - -### `IERC20` - -The default version of the ERC20 interface trait exposes `snake_case` functions: - -``` -#[starknet::interface] -pub trait IERC20 { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -### `IERC20Camel` - -On top of that, the library also offers a `camelCase` version of the same interface: - -``` -#[starknet::interface] -pub trait IERC20Camel { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - fn totalSupply(self: @TState) -> u256; - fn balanceOf(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transferFrom( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} -``` - -← Presets - -Counterfactual Deployments → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/introspection - -## Introspection - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Introspection - -To smooth interoperability, often standards require smart contracts to implement introspection mechanisms. - -In Ethereum, the EIP165 standard defines how contracts should declare -their support for a given interface, and how other contracts may query this support. - -Starknet offers a similar mechanism for interface introspection defined by the SRC5 standard. - -## SRC5 - -Similar to its Ethereum counterpart, the SRC5 standard requires contracts to implement the `supports_interface` function, -which can be used by others to query if a given interface is supported. - -### Usage - -To expose this functionality, the contract must implement the SRC5Component, which defines the `supports_interface` function. -Here is an example contract: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl SRC5InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.src5.register_interface(selector!("some_interface")); - } -} -``` - -### Interface - -``` -#[starknet::interface] -pub trait ISRC5 { - /// Query if a contract implements an interface. - /// Receives the interface identifier as specified in SRC-5. - /// Returns `true` if the contract implements `interface_id`, `false` otherwise. - fn supports_interface(interface_id: felt252) -> bool; -} -``` - -## Computing the interface ID - -The interface ID, as specified in the standard, is the XOR of all the -Extended Function Selectors -of the interface. We strongly advise reading the SNIP to understand the specifics of computing these -extended function selectors. There are tools such as src5-rs that can help with this process. - -## Registering interfaces - -For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this: - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::src5::SRC5Component; - - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - impl InternalImpl = SRC5Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - SRC5Event: SRC5Component::Event - } - - #[constructor] - fn constructor(ref self: ContractState) { - // Register the contract's support for the ISRC6 interface - self.src5.register_interface(interface::ISRC6_ID); - } - - (...) -} -``` - -## Querying interfaces - -Use the `supports_interface` function to query a contract’s support for a given interface. - -``` -#[starknet::contract] -mod MyContract { - use openzeppelin_account::interface; - use openzeppelin_introspection::interface::ISRC5DispatcherTrait; - use openzeppelin_introspection::interface::ISRC5Dispatcher; - use starknet::ContractAddress; - - #[storage] - struct Storage {} - - #[external(v0)] - fn query_is_account(self: @ContractState, target: ContractAddress) -> bool { - let dispatcher = ISRC5Dispatcher { contract_address: target }; - dispatcher.supports_interface(interface::ISRC6_ID) - } -} -``` - -| | | -| --- | --- | -| | If you are unsure whether a contract implements SRC5 or not, you can follow the process described in here. | - -← API Reference - -Migrating ERC165 to SRC5 → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/presets - -## Presets - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Presets - -Presets are ready-to-deploy contracts provided by the library. Since presets are intended to be very simple -and as generic as possible, there’s no support for custom or complex contracts such as `ERC20Pausable` or `ERC721Mintable`. - -| | | -| --- | --- | -| | For contract customization and combination of modules you can use Wizard for Cairo, our code-generation tool. | - -## Available presets - -List of available presets and their corresponding Sierra class hashes. Like Contracts for Cairo, -use of preset contracts are subject to the terms of the -MIT License. - -| | | -| --- | --- | -| | Class hashes were computed using cairo 2.11.2. | - -| Name | Sierra Class Hash | -| --- | --- | -| `AccountUpgradeable` | `0x07bdb254c058d952ce0a7447911514717c0c390a3a9c39215981bad421860a58` | -| `ERC20Upgradeable` | `0x07af6b75c3ae627338a222e534589db217880398b2194b8710f24a649d4baee7` | -| `ERC721Upgradeable` | `0x0511ec65c43f2bdf5ed24d63342aad96849f34a64e29ce05f8fcb4ef37cc22dc` | -| `ERC1155Upgradeable` | `0x045fd9554115e724d3ef018b8db08a60f13444f3e14eef9cd45f3372867d0b59` | -| `EthAccountUpgradeable` | `0x02d0d36adf3157b5b7962a69484a64d2721f43d5db519ab8a9cdd09e8f93f04c` | -| `UniversalDeployer` | `0x050e13234a6abdb992c9e07aab8b88c7067b8acb243d2e2d08eeb6b8ccd26c7c` | -| `VestingWallet` | `0x0745aa2994fd595de93fa4fdbfdff3f8d8d7ebf2067f33ec90a93e039c4a2f32` | - -| | | -| --- | --- | -| | starkli class-hash command can be used to compute the class hash from a Sierra artifact. | - -## Usage - -These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. -Simply deploy the preset class hash and add the appropriate constructor arguments. -Deploying the ERC20Upgradeable preset with starkli, for example, will look like this: - -``` -starkli deploy 0x07af6b75c3ae627338a222e534589db217880398b2194b8710f24a649d4baee7 \ - \ - --network="sepolia" -``` - -If a class hash has yet to be declared, copy/paste the preset contract code and declare it locally. -Start by setting up a project and installing the Contracts for Cairo library. -Copy the target preset contract from the presets directory and paste it in the new project’s `src/lib.cairo` like this: - -``` -// src/lib.cairo - -#[starknet::contract] -mod ERC20Upgradeable { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ContractAddress, ClassHash}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - // Upgradeable - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - fixed_supply: u256, - recipient: ContractAddress, - owner: ContractAddress - ) { - self.ownable.initializer(owner); - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, fixed_supply); - } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - self.ownable.assert_only_owner(); - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -Next, compile the contract. - -``` -scarb build -``` - -Finally, declare the preset. - -``` -starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ - --network="sepolia" -``` - -← Components - -Interfaces and Dispatchers → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/security - -## Security - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Security - -The following documentation provides context, reasoning, and examples of modules found under `openzeppelin_security`. - -| | | -| --- | --- | -| | Expect these modules to evolve. | - -## Initializable - -The Initializable component provides a simple mechanism that mimics -the functionality of a constructor. -More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. - -### Usage - -You can use the component in your contracts like this: - -``` -#[starknet::contract] -mod MyInitializableContract { - use openzeppelin_security::InitializableComponent; - - component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); - - impl InternalImpl = InitializableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - initializable: InitializableComponent::Storage, - param: felt252 - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - InitializableEvent: InitializableComponent::Event - } - - fn initializer(ref self: ContractState, some_param: felt252) { - // Makes the method callable only once - self.initializable.initialize(); - - // Initialization logic - self.param.write(some_param); - } -} -``` - -| | | -| --- | --- | -| | This Initializable pattern should only be used in one function. | - -### Interface - -The component provides the following external functions as part of the `InitializableImpl` implementation: - -``` -#[starknet::interface] -pub trait InitializableABI { - fn is_initialized() -> bool; -} -``` - -## Pausable - -The Pausable component allows contracts to implement an emergency stop mechanism. -This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. - -To become pausable, the contract should include `pause` and `unpause` functions (which should be protected). -For methods that should be available only when paused or not, insert calls to `assert_paused` and `assert_not_paused` -respectively. - -### Usage - -For example (using the Ownable component for access control): - -``` -#[starknet::contract] -mod MyPausableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_security::PausableComponent; - use starknet::ContractAddress; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - - // Pausable - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - pausable: PausableComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - PausableEvent: PausableComponent::Event - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[external(v0)] - fn pause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.pause(); - } - - #[external(v0)] - fn unpause(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.pausable.unpause(); - } - - #[external(v0)] - fn when_not_paused(ref self: ContractState) { - self.pausable.assert_not_paused(); - // Do something - } - - #[external(v0)] - fn when_paused(ref self: ContractState) { - self.pausable.assert_paused(); - // Do something - } -} -``` - -### Interface - -The component provides the following external functions as part of the `PausableImpl` implementation: - -``` -#[starknet::interface] -pub trait PausableABI { - fn is_paused() -> bool; -} -``` - -## Reentrancy Guard - -A reentrancy attack occurs when the caller is able to obtain more resources than allowed by recursively calling a target’s function. - -### Usage - -Since Cairo does not support modifiers like Solidity, the ReentrancyGuard -component exposes two methods `start` and `end` to protect functions against reentrancy attacks. -The protected function must call `start` before the first function statement, and `end` before the return statement, as shown below: - -``` -#[starknet::contract] -mod MyReentrancyContract { - use openzeppelin_security::ReentrancyGuardComponent; - - component!( - path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent - ); - - impl InternalImpl = ReentrancyGuardComponent::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - reentrancy_guard: ReentrancyGuardComponent::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ReentrancyGuardEvent: ReentrancyGuardComponent::Event - } - - #[external(v0)] - fn protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } - - #[external(v0)] - fn another_protected_function(ref self: ContractState) { - self.reentrancy_guard.start(); - - // Do something - - self.reentrancy_guard.end(); - } -} -``` - -| | | -| --- | --- | -| | The guard prevents the execution flow occurring inside `protected_function` to call itself or `another_protected_function`, and vice versa. | - -← Merkle Tree - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/udc - -## Universal Deployer Contract - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Universal Deployer Contract - -The Universal Deployer Contract (UDC) is a singleton smart contract that wraps the deploy syscall to expose it to any contract that doesn’t implement it, such as account contracts. You can think of it as a standardized generic factory for Starknet contracts. - -Since Starknet has no deployment transaction type, it offers a standardized way to deploy smart contracts by following the Standard Deployer Interface and emitting a ContractDeployed event. - -For details on the motivation and the decision making process, see the Universal Deployer Contract proposal. - -## UDC contract address - -This version of the UDC is not deployed yet. Check the current deployed version here. - -## Interface - -``` -#[starknet::interface] -pub trait IUniversalDeployer { - fn deploy_contract( - class_hash: ClassHash, - salt: felt252, - from_zero: bool, - calldata: Span - ) -> ContractAddress; -} -``` - -## Deploying a contract with the UDC - -First, declare the target contract (if it’s not already declared). -Next, call the UDC’s `deploy_contract` method. -Here’s an implementation example in Cairo: - -``` -use openzeppelin_utils::interfaces::{IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait}; - -const UDC_ADDRESS: felt252 = 0x04...; - -fn deploy() -> ContractAddress { - let dispatcher = IUniversalDeployerDispatcher { - contract_address: UDC_ADDRESS.try_into().unwrap() - }; - - // Deployment parameters - let class_hash = class_hash_const::< - 0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6 - >(); - let salt = 1234567879; - let not_from_zero = true; - let calldata = array![]; - - // The UDC returns the deployed contract address - dispatcher.deploy_contract(class_hash, salt, not_from_zero, calldata.span()) -} -``` - -## Deployment types - -The Universal Deployer Contract offers two types of addresses to deploy: origin-dependent and origin-independent. -As the names suggest, the origin-dependent type includes the deployer’s address in the address calculation, -whereas, the origin-independent type does not. -The `from_zero` boolean parameter ultimately determines the type of deployment. - -| | | -| --- | --- | -| | When deploying a contract that uses `get_caller_address` in the constructor calldata, remember that the UDC, not the account, deploys that contract. Therefore, querying `get_caller_address` in a contract’s constructor returns the UDC’s address, *not the account’s address*. | - -### Origin-dependent - -By making deployments dependent upon the origin address, users can reserve a whole address space to prevent someone else from taking ownership of the address. - -Only the owner of the origin address can deploy to those addresses. - -Achieving this type of deployment necessitates that the origin sets `from_zero` to `false` in the deploy\_contract call. -Under the hood, the function passes a modified salt to the `deploy_syscall`, which is the hash of the origin’s address with the given salt. - -To deploy a unique contract address pass: - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, true, calldata.span()); -``` - -### Origin-independent - -Origin-independent contract deployments create contract addresses independent of the deployer and the UDC instance. -Instead, only the class hash, salt, and constructor arguments determine the address. -This type of deployment enables redeployments of accounts and known systems across multiple networks. -To deploy a reproducible deployment, set `from_zero` to `true`. - -``` -let deployed_addr = udc.deploy_contract(class_hash, salt, false, calldata.span()); -``` - -## Version changes - -| | | -| --- | --- | -| | See the previous Universal Deployer API for the initial spec. | - -The latest iteration of the UDC includes some notable changes to the API which include: - -* `deployContract` method is replaced with the snake\_case deploy\_contract. -* `unique` parameter is replaced with `not_from_zero` in both the `deploy_contract` method and ContractDeployed event. - -## Precomputing contract addresses - -This library offers utility functions written in Cairo to precompute contract addresses. -They include the generic calculate\_contract\_address\_from\_deploy\_syscall as well as the UDC-specific calculate\_contract\_address\_from\_udc. -Check out the deployments for more information. - -← Common - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/upgrades - -## Upgrades - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Upgrades - -In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns. - -Starknet has native upgradeability through a syscall that updates the contract source code, removing the need for proxies. - -| | | -| --- | --- | -| | Make sure you follow our security recommendations before upgrading. | - -## Replacing contract classes - -To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class. - -Contract Classes represent the source code of a program. All contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by a class hash, and before a contract of a class can be deployed, the class hash needs to be declared. - -### `replace_class_syscall` - -The `replace_class` syscall allows a contract to update its source code by replacing its class hash once deployed. - -``` -/// Upgrades the contract source code to the new contract class. -fn upgrade(new_class_hash: ClassHash) { - assert(!new_class_hash.is_zero(), 'Class hash cannot be zero'); - starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); -} -``` - -| | | -| --- | --- | -| | If a contract is deployed without this mechanism, its class hash can still be replaced through library calls. | - -## `Upgradeable` component - -OpenZeppelin Contracts for Cairo provides Upgradeable to add upgradeability support to your contracts. - -### Usage - -Upgrades are often very sensitive operations, and some form of access control is usually required to -avoid unauthorized upgrades. The Ownable module is used in this example. - -| | | -| --- | --- | -| | We will be using the following module to implement the IUpgradeable interface described in the API Reference section. | - -``` -#[starknet::contract] -mod UpgradeableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::ClassHash; - use starknet::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; - - #[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 - } - - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); - } - - #[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(); - - // Replace the class hash upgrading the contract - self.upgradeable.upgrade(new_class_hash); - } - } -} -``` - -## Security - -Upgrades can be very sensitive operations, and security should always be top of mind while performing one. Please make sure you thoroughly review the changes and their consequences before upgrading. Some aspects to consider are: - -* API changes that might affect integration. For example, changing an external function’s arguments might break existing contracts or offchain systems calling your contract. -* Storage changes that might result in lost data (e.g. changing a storage slot name, making existing storage inaccessible). -* Collisions (e.g. mistakenly reusing the same storage slot from another component) are also possible, although less likely if best practices are followed, for example prepending storage variables with the component’s name (e.g. `ERC20_balances`). -* Always check for backwards compatibility before upgrading between versions of OpenZeppelin Contracts. - -## Proxies in Starknet - -Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them. - -In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement. - -Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used -for redirecting every potential function call to the implementation. This means that a generic proxy contract -can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward -their execution to another contract class. -This can still be useful for example to upgrade the logic of some functions. - -← API Reference - -API Reference → - ---- - -**Source URL:** https://docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.0/wizard - -## Wizard for Cairo - OpenZeppelin Docs - -You are not reading the current version of this documentation. 2.0.0 is the current version. - -# Wizard for Cairo - -Not sure where to start? Use the interactive generator below to bootstrap your -contract and learn about the components offered in OpenZeppelin Contracts for Cairo. - -| | | -| --- | --- | -| | We strongly recommend checking the Components section to understand how to extend from our library. | - -← Overview - -Components → - ---- diff --git a/python/src/cairo_coder/optimizers/retrieval_optimizer.py b/python/src/cairo_coder/optimizers/retrieval_optimizer.py index 6b9486eb..827eb2f4 100644 --- a/python/src/cairo_coder/optimizers/retrieval_optimizer.py +++ b/python/src/cairo_coder/optimizers/retrieval_optimizer.py @@ -111,7 +111,8 @@ def _(dspy, os, retriever): data = [dspy.Example(**d).with_inputs("query") for d in example_dataset] # Take maximum 300 random values from the dataset - random.Random(0).shuffle(data) + random.seed(42) + random.shuffle(data) data = data[0:300] train_set = data[: int(len(data) * 0.33)] val_set = data[int(len(data) * 0.33) : int(len(data) * 0.66)] @@ -337,7 +338,7 @@ def _(evaluate, optimized_program): @app.cell def _(RETRIEVER, os): - compiled_program_path = "./dspy_program/program.json" + compiled_program_path = "optimizers/results/optimized_retrieval_program.json" if not os.path.exists(compiled_program_path): raise FileNotFoundError(f"{compiled_program_path} not found") diff --git a/python/src/scripts/summarizer/cli.py b/python/src/scripts/summarizer/cli.py index a397b28d..0c512a87 100644 --- a/python/src/scripts/summarizer/cli.py +++ b/python/src/scripts/summarizer/cli.py @@ -8,9 +8,9 @@ import typer from dotenv import load_dotenv -from .base_summarizer import SummarizerConfig -from .header_fixer import HeaderFixer -from .summarizer_factory import DocumentationType, SummarizerFactory +from scripts.summarizer.base_summarizer import SummarizerConfig +from scripts.summarizer.header_fixer import HeaderFixer +from scripts.summarizer.summarizer_factory import DocumentationType, SummarizerFactory # Load environment variables load_dotenv() diff --git a/python/src/scripts/summarizer/dpsy_summarizer.py b/python/src/scripts/summarizer/dpsy_summarizer.py index 4d35be8b..89c60f7f 100644 --- a/python/src/scripts/summarizer/dpsy_summarizer.py +++ b/python/src/scripts/summarizer/dpsy_summarizer.py @@ -14,20 +14,9 @@ # Initialize DSPy configuration -def configure_dspy(provider: str = "gemini", model: str = "gemini/gemini-2.5-flash-lite-preview-06-17", temperature: float = 0.50): +def configure_dspy(provider: str = "gemini", model: str = "gemini/gemini-2.5-flash-lite", temperature: float = 0.50): """Configure DSPy with the specified provider and model""" - api_key = None - if provider == "gemini": - api_key = os.getenv('GEMINI_API_KEY') - elif provider == "openai": - api_key = os.getenv('OPENAI_API_KEY') - elif provider == "anthropic": - api_key = os.getenv('ANTHROPIC_API_KEY') - - if not api_key: - raise ValueError(f"API key not found for provider: {provider}") - - lm = dspy.LM(model, api_key=api_key, max_tokens=30000, temperature=temperature) + lm = dspy.LM(model, max_tokens=30000, temperature=temperature) dspy.settings.configure(lm=lm) class ProduceGist(dspy.Signature): diff --git a/python/src/scripts/summarizer/generated/cairo_book_summary.md b/python/src/scripts/summarizer/generated/cairo_book_summary.md index cc6291bc..b81a4a4d 100644 --- a/python/src/scripts/summarizer/generated/cairo_book_summary.md +++ b/python/src/scripts/summarizer/generated/cairo_book_summary.md @@ -6,89 +6,82 @@ What is Cairo? # What is Cairo? -## Core Concepts +## Overview -Cairo is a programming language designed to leverage mathematical proofs for computational integrity. It enables programs to prove they have performed computations correctly, even on untrusted machines. The language is built on STARK technology, which transforms computational claims into constraint systems, with the ultimate goal of generating verifiable mathematical proofs that can be efficiently verified with certainty. +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. -## Applications +## Core Technology and Applications -Cairo's primary application is Starknet, a Layer 2 scaling solution for Ethereum. Starknet utilizes Cairo's proof system to address scalability challenges: computations are executed off-chain by a prover, who generates a STARK proof. This proof is then verified by an Ethereum smart contract, requiring significantly less computational power than re-executing the original computations. This allows for massive scalability while maintaining security. +- **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. -Beyond blockchain, Cairo's verifiable computation capabilities can benefit any scenario where computational integrity needs efficient verification. +## Design Philosophy -Learning Cairo +- **Provable Programs:** Cairo is a general-purpose programming language specifically engineered for creating provable programs. It abstracts away the underlying cryptographic complexities, allowing developers to focus on program logic without needing deep expertise in cryptography or complex mathematics. +- **Rust Inspiration:** The language is strongly inspired by Rust, aiming for a developer-friendly experience. +- **Performance:** Powered by a Rust VM and a next-generation prover, Cairo offers fast execution and proof generation, making it suitable for building provable applications. -# Learning Cairo +## Target Audience -This book is designed for developers with a basic understanding of programming concepts. While prior experience with Rust can be helpful due to similarities, it is not required. +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. -## Learning Paths +Getting Started with the Cairo Book -### For General-Purpose Developers +# Getting Started with the Cairo Book -Focus on chapters 1-12 for core language features and programming concepts, excluding deep dives into smart contract specifics. +This section outlines recommended reading paths through the Cairo book based on your background and goals. -### For New Smart Contract Developers +## For General-Purpose Developers -Read the book from beginning to end to establish a strong foundation in both Cairo fundamentals and smart contract development. +Focus on chapters 1-12, which cover core language features and programming concepts without deep dives into smart contract specifics. -### For Experienced Smart Contract Developers +## For New Smart Contract Developers -Follow a focused path: +Read the book from beginning to end to build a solid foundation in both Cairo language fundamentals and smart contract development principles. -- Chapters 1-3 for Cairo basics. -- Chapter 8 for Cairo's trait and generics system. -- Chapter 15 for smart contract development. -- Reference other chapters as needed. - -## Prerequisites - -Basic programming knowledge, including variables, functions, and common data structures, is assumed. - -Cairo's Foundation +## For Experienced Smart Contract Developers -# Cairo's Foundation +A focused path is recommended: -Proof systems like zk-SNARKs utilize arithmetic circuits over a finite field \\(F_p\\), employing constraints at specific gates represented by equations: - -\\[ -(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 -\\] +- Chapters 1-3: Cairo basics +- Chapter 8: Cairo's trait and generics system +- Chapter 15: Smart contract development +- Reference other chapters as needed. -Here, \\(s_1, ..., s_n\\) are signals, and \\(a_i, b_i, c_i\\) are coefficients. A witness is an assignment of signals that satisfies all circuit constraints. zk-SNARK proofs leverage this to prove knowledge of a witness without revealing private input signals, ensuring prover honesty and privacy. +### Prerequisites -In contrast, STARKs, which Cairo employs, use an Algebraic Intermediate Representation (AIR). AIR defines computations through polynomial constraints. By enabling emulated arithmetic circuits, Cairo can facilitate the implementation of zk-SNARKs proof verification within STARK proofs. +Basic programming knowledge (variables, functions, data structures) is assumed. Prior experience with Rust is helpful but not required. -Resources and Setup +Cairo's Architecture -# Resources and Setup +# Cairo's Architecture -This guide assumes the use of Cairo version 2.11.4 and Starknet Foundry version 0.39.0. For installation or updates, refer to the "Installation" section of Chapter 1. +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. -## Additional Resources +## Components of Cairo -- **Cairo Playground**: A browser-based environment for writing, compiling, debugging, and proving Cairo code without local 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, which includes essential types, traits, and utilities available in all Cairo projects. -- **Cairo Package Registry**: Hosts reusable Cairo libraries like Alexandria and Open Zeppelin Contracts for Cairo, manageable through Scarb for streamlined development. -- **Scarb Documentation**: Official documentation for Cairo's package manager and build tool, covering package creation, dependency management, builds, and project configuration. +Cairo comprises three primary components: -Setting up the Cairo Development Environment +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. -Introduction to the Cairo Development Environment +## Arithmetic Intermediate Representation (AIR) -# Introduction to the Cairo Development Environment +AIR is an arithmetization technique fundamental to proof systems. While STARKs use AIRs, other systems might employ different techniques like R1CS or PLONKish arithmetization. -Installing Cairo Development Tools +Getting Started with Cairo -# Installing Cairo Development Tools +Introduction and Setup -The first step to getting started with Cairo is to install the necessary development tools. This involves installing `starkup`, a command-line tool for managing Cairo versions, which in turn installs Scarb (Cairo's build toolchain and package manager) and Starknet Foundry. +# Introduction and Setup -## Installing `starkup` +## Installing Cairo -`starkup` helps manage Cairo, Scarb, and Starknet Foundry. +Cairo is installed using `starkup`, a command-line tool for managing Cairo versions and associated tools. An internet connection is required for the download. -### On Linux or macOS +### Installing `starkup` on Linux or MacOs Open a terminal and run the following command: @@ -96,58 +89,80 @@ Open a terminal and run the following command: curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh ``` -This command downloads and runs an installation script. Upon successful installation, you will see: +This script installs the `starkup` tool, which in turn installs the latest stable versions of Cairo, Scarb, and Starknet Foundry. A success message will appear upon completion: ```bash starkup: Installation complete. ``` -## Scarb and Starknet Foundry - -Scarb is Cairo's package manager and build system, inspired by Rust's Cargo. It bundles the Cairo compiler and language server, simplifying tasks like building code, managing dependencies, and providing Language Server Protocol (LSP) support for IDEs. - -Starknet Foundry is a toolchain for developing Cairo programs and Starknet smart contracts, offering features for writing and running tests, deploying contracts, and interacting with the Starknet network. - ### Verifying Installations -After `starkup` installation, open a new terminal session and verify the installations: +After `starkup` completes, open a new terminal session and verify the installations: ```bash $ scarb --version -scarb 2.11.4 (c0ef5ec6a 2025-04-09) -cairo: 2.11.4 (https://crates.io/crates/cairo-lang-compiler/2.11.4) +scarb 2.12.0 (639d0a65e 2025-08-04) +cairo: 2.12.0 (https://crates.io/crates/cairo-lang-compiler/2.12.0) sierra: 1.7.0 $ snforge --version -snforge 0.39.0 +snforge 0.48.0 ``` -## VSCode Extension +## 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. -Cairo offers a VSCode extension that provides syntax highlighting, code completion, and other development features. +## Starknet Foundry -### Installation and Configuration +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. -1. Install the Cairo extension from the [VSCode Marketplace][vsc extension]. -2. Open the extension's settings in VSCode. -3. Enable the `Enable Language Server` and `Enable Scarb` options. +## VSCode Extension + +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. [vsc extension]: https://marketplace.visualstudio.com/items?itemName=starkware.cairo1 -Creating and Structuring Cairo Projects +## Cairo Editions + +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. + +| 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 + +For questions about Starknet or Cairo, use the [Starknet Discord server][discord]. + +[discord]: https://discord.gg/starknet-community -# Creating and Structuring Cairo Projects +## Starknet AI Agent + +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 is recommended to create a dedicated directory for your Cairo projects. For Linux, macOS, and PowerShell on Windows, use: +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 mkdir ~/cairo_projects cd ~/cairo_projects ``` -For Windows CMD, use: +For Windows CMD: ```cmd > mkdir "%USERPROFILE%\cairo_projects" @@ -156,98 +171,102 @@ For Windows CMD, use: ## Creating a Project with Scarb -Once you are in your project directory, you can create a new Cairo project using Scarb: +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. ```bash scarb new hello_world ``` -Configuring Cairo Projects with Scarb.toml - -# Configuring Cairo Projects with Scarb.toml - -Scarb uses the `Scarb.toml` file, written in TOML format, to configure Cairo projects. +This command generates a new directory named `hello_world` containing the following files and directories: -## Project Manifest (`Scarb.toml`) +- `Scarb.toml`: The project's manifest file. +- `src/lib.cairo`: The main library file. +- `tests/`: A directory for tests. -The `Scarb.toml` file contains essential information for Scarb to compile your project. +Scarb also initializes a Git repository. You can skip this with the `--no-vcs` flag during project creation. -### `[package]` Section +### `Scarb.toml` Configuration -This section defines the package's metadata: +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: -- `name`: The name of the package. -- `version`: The package version. -- `edition`: The edition of the Cairo prelude to use. - -### `[dependencies]` Section - -This section lists the project's dependencies, which are referred to as crates in Cairo. For example, `starknet = "2.11.4"` or `cairo_execute = "2.11.4"`. +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2024_07" -### `[dev-dependencies]` Section +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html -Dependencies required for development and testing, but not for the production build. Examples include `snforge_std` and `assert_macros` for testing with Starknet Foundry, or `cairo_test`. +[dependencies] +starknet = "2.12.0" -### `[cairo]` Section +[dev-dependencies] +snforge_std = "0.48.0" +assert_macros = "2.12.0" -This section allows for Cairo-specific configurations. +[[target.starknet-contract]] +sierra = true -- `enable-gas = false`: Disables gas tracking, which is necessary for executable targets as gas is specific to Starknet contracts. +[scripts] +test = "snforge test" +``` -### Target Configurations +### Project Structure -Cairo projects can be configured to build different types of targets. +Scarb enforces a standard project structure: -#### `[[target.starknet-contract]]` +```txt +├── Scarb.toml +├── src +│ ├── lib.cairo +│ └── hello_world.cairo +``` -This section is used to build Starknet smart contracts. It typically includes `sierra = true`. +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]]` +### Writing Your First Program -This section specifies that the package compiles to a Cairo executable. +To create a simple "Hello, World!" program, you'll modify the `src/lib.cairo` and create a new file `src/hello_world.cairo`. -- `name`: The name of the executable. -- `function`: The entry point function for the executable. +First, update `src/lib.cairo` to declare the `hello_world` module: -### `[scripts]` Section +```cairo,noplayground +mod hello_world; +``` -This section allows defining custom scripts. A default script for running tests using `snforge` is often included as `test = "snforge test"`. +Then, create `src/hello_world.cairo` with the following content: -## Example `Scarb.toml` Configurations +```cairo +#[executable] +fn main() { + println!("Hello, World!"); +} +``` -### Default `scarb new` Output (with Starknet Foundry) +This code defines an executable function `main` that prints "Hello, World!" to the console. -When creating a new project with Starknet Foundry, `scarb new` generates a `Scarb.toml` file similar to this: +## Building a Scarb Project -Filename: Scarb.toml +To build your project, navigate to the project's root directory (e.g., `hello_world`) and run: -```toml -[package] -name = "hello_world" -version = "0.1.0" -edition = "2024_07" +```bash +scarb build +``` -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +This command compiles your Cairo code. The output will indicate the compilation progress and completion. -[dependencies] -starknet = "2.11.4" +## Setting Up an Executable Project (Example: `prime_prover`) -[dev-dependencies] -snforge_std = "0.44.0" -assert_macros = "2.11.4" +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. -[[target.starknet-contract]] -sierra = true +Create a new project: -[scripts] -test = "snforge test" +```bash +scarb new prime_prover +cd prime_prover ``` -### Modified `Scarb.toml` for Executable Programs - -To create an executable program that can be proven, `Scarb.toml` needs to be modified to define an executable target and include necessary plugins like `cairo_execute`. - -Filename: Scarb.toml +Modify `Scarb.toml` to include an executable target and the `cairo_execute` dependency: ```toml [package] @@ -259,658 +278,602 @@ edition = "2024_07" enable-gas = false [dependencies] -cairo_execute = "2.11.4" - +cairo_execute = "2.12.0" [[target.executable]] name = "main" function = "prime_prover::main" ``` -Managing Cairo Project Dependencies +- `[[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. -# Managing Cairo Project Dependencies +Writing and Running Basic Programs -Dependencies are managed in the `Scarb.toml` file. +# Writing and Running Basic Programs -## Declaring Dependencies +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. -You can declare dependencies within a `[dependencies]` section. If you need to import multiple packages, list them all under a single `[dependencies]` section. Development dependencies can be declared in a separate `[dev-dependencies]` section. - -The following example shows importing a specific branch, which is deprecated and should not be used: +```shell +$ scarb execute + 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! -```cairo -[dependencies] -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", branch = "cairo-v2.3.0-rc0" } ``` -## Fetching and Compiling - -To fetch all external dependencies and compile your package, run: +If `Hello, world!` is printed to your terminal, you have successfully written and executed your first Cairo program. -```bash -scarb build -``` +## Anatomy of a Cairo Program -## Adding and Removing Dependencies +A basic Cairo program consists of functions, with `main` being a special entry point. -You can add dependencies using the `scarb add` command, which automatically updates your `Scarb.toml` file. For development dependencies, use `scarb add --dev`. To remove a dependency, either manually edit `Scarb.toml` or use the `scarb rm` command. +### The `main` Function -## The Glob Operator +The `main` function is the starting point for execution in any executable Cairo program. -To bring all public items defined in a path into scope, use the `*` glob operator: +```cairo,noplayground +fn main() { -```rust -use core::num::traits::*; +} ``` -Be cautious when using the glob operator, as it can make it harder to track the origin of names used in your code. It is often used in testing scenarios. - -Cairo Project Structure and Execution +- `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. -Project Creation and Structure +### The `println!` Macro -# Project Creation and Structure +The body of the `main` function often contains code to perform actions, such as printing output to the screen. -A Cairo package is a directory containing a `Scarb.toml` manifest file and associated source code. You can create a new Cairo package using the `scarb new` command: - -```bash -scarb new my_package +```cairo,noplayground + println!("Hello, World!"); ``` -This command generates a new package directory with the following structure: +Key points about this line: -```text -my_package/ -├── Scarb.toml -└── src - └── lib.cairo -``` +- **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. -- `src/`: The main directory for Cairo source files. -- `src/lib.cairo`: The default root module of the crate, serving as the main entry point. -- `Scarb.toml`: The package manifest file containing metadata and configuration like dependencies, name, version, and authors. +Building a Primality Prover -The `Scarb.toml` file typically looks like this: +# Building a Primality Prover -```toml -[package] -name = "my_package" -version = "0.1.0" -edition = "2024_07" +## Proving That A Number Is Prime -[executable] +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. -[cairo] -enable-gas = false +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 enable execution and proving, add the following to your `Scarb.toml` file: +```toml [dependencies] -cairo_execute = "2.11.4" +cairo_execute = "2.12.0" ``` -You can organize your code into multiple Cairo source files by creating additional `.cairo` files within the `src` directory or its subdirectories. For example, you might have `src/lib.cairo` declare modules that are implemented in other files like `src/hello_world.cairo`. +### Writing the Prime-Checking Logic -```cairo -// src/lib.cairo -mod hello_world; -``` +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: + +Filename: src/lib.cairo ```cairo -// src/hello_world.cairo +/// Checks if a number is prime +/// +/// # Arguments +/// +/// * `n` - The number to check +/// +/// # Returns +/// +/// * `true` if the number is prime +/// * `false` if the number is not prime +fn is_prime(n: u32) -> bool { + if n <= 1 { + return false; + } + if n == 2 { + return true; + } + if n % 2 == 0 { + return false; + } + let mut i = 3; + loop { + if i * i > n { + return true; + } + if n % i == 0 { + return false; + } + i += 2; + } +} + +// Executable entry point #[executable] -fn main() { - println!("Hello, World!"); +fn main(input: u32) -> bool { + is_prime(input) } ``` -To build your project, navigate to the project's root directory and run: +#### Explanation of the Code: -```bash -scarb build -``` +The `is_prime` function: -Building and Running Cairo Programs +- 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. -# Building and Running Cairo Programs +The `main` function: -To build a Cairo project, use the `scarb build` command, which generates the compiled Sierra code. To execute a Cairo program, use the `scarb execute` command. These commands are consistent across different operating systems. +- 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. -## Running the "Hello, World!" Program +Conclusion and Resources -Executing `scarb execute` will compile and run the program. The expected output is: +# Conclusion and Resources -```shell -$ scarb execute - Compiling hello_world v0.1.0 (listings/ch01-getting-started/no_listing_01_hello_world/Scarb.toml) - Finished `dev` profile target(s) in 4 seconds - Executing hello_world -Hello, World! +Congratulations on building your first Cairo program! You've successfully: + +- 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`. + +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: + +```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". ``` -If "Hello, World!" is printed, the program has run successfully. +If the program panics, no proof can be generated, and thus, verification is not possible. -## Anatomy of a Cairo Program +## Additional Resources -A basic Cairo program includes a `main` function, which is the entry point: +To continue your journey with Cairo, explore these resources: -```cairo,noplayground -fn main() { +- **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. -} -``` +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). -- `fn main()`: Declares a function named `main` with no parameters and no return value. The function body must be enclosed in curly brackets `{}`. -- `println!("Hello, World!");`: This macro prints the string "Hello, World!" to the terminal. +Cairo Language Fundamentals -For consistent code style, the `scarb fmt` command can be used for automatic formatting. +Cairo Language Fundamentals -Zero-Knowledge Proof Generation +# Cairo Language Fundamentals -# Zero-Knowledge Proof Generation +## Keywords and Built-ins -To generate a zero-knowledge proof for the primality check program, you first need to execute the program to create the necessary artifacts. +Cairo has several types of keywords and built-in functions: -## Executing the Program +### Keywords -You can run the program using the `scarb execute` command, providing the package name and input arguments. For example, to check if 17 is prime: +- **`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. -```bash -scarb execute -p prime_prover --print-program-output --arguments 17 -``` +### Reserved Keywords -- `-p prime_prover`: Specifies the package name. -- `--print-program-output`: Displays the program's result. -- `--arguments 17`: Passes 17 as the input number. +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 output will indicate success (0) and the primality result (1 for prime, 0 for not prime). +### Built-in Functions -```bash -$ 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 4 seconds - Executing prime_prover -Program output: -1 +These functions have special purposes and should not be used as names for other items: +- **`assert`**: Checks a boolean expression; triggers `panic` if false. +- **`panic`**: Acknowledges an error and terminates the program. -$ scarb execute -p prime_prover --print-program-output --arguments 4 -[0, 0] # 4 is not prime -$ scarb execute -p prime_prover --print-program-output --arguments 23 -[0, 1] # 23 is prime -``` +## Operators and Symbols -This execution generates files such as `air_public_input.json`, `air_private_input.json`, `trace.bin`, and `memory.bin` in the `./target/execute/prime_prover/execution1/` directory, which are required for proving. +Cairo uses various operators and symbols for different purposes. -## Generating a Zero-Knowledge Proof +### Operators -Cairo 2.10 integrates the Stwo prover via Scarb, enabling direct proof generation. To create the proof, use the `scarb prove` command, referencing the execution ID: +Operators have specific explanations and may be overloadable with associated traits: -```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 -``` +| Operator | Example | Explanation | Overloadable? | +| :------- | :------------------------- | :--------------------------------------- | :------------ | -------------------------------- | ------- | --------------------------- | --- | +| `!` | `!expr` | Logical complement | `Not` | +| `~` | `~expr` | Bitwise NOT | `BitNot` | +| `!=` | `expr != expr` | Non-equality comparison | `PartialEq` | +| `%` | `expr % expr` | Arithmetic remainder | `Rem` | +| `%=` | `var %= expr` | Arithmetic remainder and assignment | `RemEq` | +| `&` | `expr & expr` | Bitwise AND | `BitAnd` | +| `&&` | `expr && expr` | Short-circuiting logical AND | | +| `*` | `expr * expr` | Arithmetic multiplication | `Mul` | +| `*=` | `var *= expr` | Arithmetic multiplication and assignment | `MulEq` | +| `@` | `@var` | Snapshot | | +| `*` | `*var` | Desnap | | +| `+` | `expr + expr` | Arithmetic addition | `Add` | +| `+=` | `var += expr` | Arithmetic addition and assignment | `AddEq` | +| `,` | `expr, expr` | Argument and element separator | | +| `-` | `-expr` | Arithmetic negation | `Neg` | +| `-` | `expr - expr` | Arithmetic subtraction | `Sub` | +| `-=` | `var -= expr` | Arithmetic subtraction and assignment | `SubEq` | +| `->` | `fn(...) -> type`, ` | ... | -> type` | Function and closure return type | | +| `.` | `expr.ident` | Member access | | +| `/` | `expr / expr` | Arithmetic division | `Div` | +| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | +| `:` | `pat: type`, `ident: type` | Constraints | | +| `:` | `ident: expr` | Struct field initializer | | +| `;` | `expr;` | Statement and item terminator | | +| `<` | `expr < expr` | Less than comparison | `PartialOrd` | +| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | +| `=` | `var = expr` | Assignment | | +| `==` | `expr == expr` | Equality comparison | `PartialEq` | +| `=>` | `pat => expr` | Part of match arm syntax | | +| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | +| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | +| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | +| ` | ` | `expr | expr` | Bitwise OR | `BitOr` | +| ` | | ` | `expr | | expr` | Short-circuiting logical OR | | +| `?` | `expr?` | Error propagation | | + +### Non-Operator Symbols -The generated proof will be saved in `target/execute/prime_prover/execution1/proof/proof.json`. - -Basic Cairo Programming Concepts - -Cairo Keywords, Operators, and Symbols - -# Cairo Keywords, Operators, and Symbols - -Cairo keywords are reserved for current or future use and are categorized into strict, loose, and reserved. - -## Strict Keywords - -These keywords can only be used in their correct contexts and cannot be used as names of any items. - -- `as`: Rename import -- `break`: Exit a loop immediately -- `const`: Define constant items -- `continue`: Continue to the next loop iteration -- `else`: Fallback for `if` and `if let` control flow constructs -- `enum`: Define an enumeration -- `extern`: Function defined at the compiler level that can be compiled to CASM -- `false`: Boolean false literal -- `fn`: Define a function -- `if`: Branch based on the result of a conditional expression -- `impl`: Implement inherent or trait functionality -- `implicits`: Special kind of function parameters that are required to perform certain actions -- `let`: Bind a variable -- `loop`: Loop unconditionally -- `match`: Match a value to patterns -- `mod`: Define a module -- `mut`: Denote variable mutability -- `nopanic`: Functions marked with this notation mean that the function will never panic. -- `of`: Implement a trait -- `pub`: Denote public visibility in items, such as struct and struct fields, enums, consts, traits and impl blocks, or modules -- `ref`: Parameter passed implicitly returned at the end of a function -- `return`: Return from function -- `struct`: Define a structure -- `trait`: Define a trait -- `true`: Boolean true literal -- `type`: Define a type alias -- `use`: Bring symbols into scope -- `while`: loop conditionally based on the result of an expression - -## Loose Keywords - -These keywords are associated with a specific behaviour, but can also be used to define items. - -- `self`: Method subject -- `super`: Parent module of the current module - -## Reserved Keywords - -These keywords aren't used yet, but they are reserved for future use. It is recommended not to use them to ensure forward compatibility. - -- `Self` -- `do` -- `dyn` -- `for` -- `hint` -- `in` -- `macro` -- `move` -- `static_assert` -- `static` -- `try` -- `typeof` -- `unsafe` -- `where` -- `with` -- `yield` - -## Built-in Functions - -Cairo provides specific built-in functions. Using their names for other items is not recommended. - -- `assert`: Checks a boolean expression; panics if false. -- `panic`: Terminates the program due to an error. - -## Operators - -| Operator | Example | Explanation | Overloadable Trait | -| -------- | ----------------- | ------------------------------------- | ------------------ | ---------- | ------- | --------------------------- | --- | -| `+` | `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` | Function and closure return type | | -| `.` | `expr.ident` | Member access | | -| `/` | `expr / expr` | Arithmetic division | `Div` | -| `/=` | `var /= expr` | Arithmetic division and assignment | `DivEq` | -| `:` | `pat: type` | Type annotation | | -| `:` | `ident: expr` | Struct field initializer | | -| `;` | `expr;` | Statement and item terminator | | -| `<` | `expr < expr` | Less than comparison | `PartialOrd` | -| `<=` | `expr <= expr` | Less than or equal to comparison | `PartialOrd` | -| `=` | `var = expr` | Assignment | | -| `==` | `expr == expr` | Equality comparison | `PartialEq` | -| `=>` | `pat => expr` | Part of match arm syntax | | -| `>` | `expr > expr` | Greater than comparison | `PartialOrd` | -| `>=` | `expr >= expr` | Greater than or equal to comparison | `PartialOrd` | -| `^` | `expr ^ expr` | Bitwise exclusive OR | `BitXor` | -| ` | ` | `expr | expr` | Bitwise OR | `BitOr` | -| ` | | ` | `expr | | expr` | Short-circuiting logical OR | | -| `?` | `expr?` | Error propagation | | - -## Non-Operator Symbols - -### Stand-Alone Syntax +These symbols have specific meanings when used alone or within paths: | Symbol | Explanation | -| --------------------------------------- | ----------------------------------------- | +| :-------------------------------------- | :---------------------------------------- | | `..._u8`, `..._usize`, `..._bool`, etc. | Numeric literal of specific type | -| `"..."` | String literal | +| `\"...\"` | String literal | | `'...'` | Short string, 31 ASCII characters maximum | | `_` | “Ignored” pattern binding | -### Path-Related Syntax +#### Path-Related Syntax | Symbol | Explanation | -| -------------------- | ---------------------------------------------------------------- | +| :------------------- | :--------------------------------------------------------------- | | `ident::ident` | Namespace path | | `super::path` | Path relative to the parent of the current module | | `trait::method(...)` | Disambiguating a method call by naming the trait that defines it | -### Generic Type Parameters +#### 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 | -### Tuples +## Statements and Expressions -| 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(...)` | Function call expression; also used to initialize tuple `struct`s and tuple `enum` variants | +Cairo functions consist of a series of statements, optionally ending in an expression. -### Curly Braces +- **Statements**: Instructions that perform an action but do not return a value. Example: `let y = 6;`. +- **Expressions**: Evaluate to a resulting value. -| Context | Explanation | -| ------------ | ---------------- | -| `{...}` | Block expression | -| `Type {...}` | `struct` literal | +Attempting to assign a statement to a variable results in an error, as statements do not return values. -Cairo Macros and Printing +```cairo +#[executable] +fn main() { + // let x = (let y = 6); // This is an error +} +``` -# Cairo Macros and Printing +## Comments -Cairo provides several macros for various purposes, including assertions, formatting, and interacting with components. Some key macros are: +Comments are ignored by the compiler and used for human readability. -- `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. -- `assert_lt!`: Evaluates a comparison and panics if greater or equal. -- `assert_le!`: Evaluates a comparison and panics if greater. -- `assert_gt!`: Evaluates a comparison and panics if lower or equal. -- `assert_ge!`: Evaluates a comparison and panics if lower. -- `format!`: Formats a string and returns a `ByteArray`. -- `write!`: Writes formatted strings in a formatter. -- `writeln!`: Writes formatted strings in a formatter on a new line. -- `get_dep_component!`: Returns the requested component state from a snapshot. -- `get_dep_component_mut!`: Returns the requested component state from a reference. -- `component!`: Macro used in Starknet contracts to embed a component inside a contract. +### Single-line Comments -For printing, Cairo offers two main macros: +Start with `//` and continue to the end of the line. -- `println!`: Prints text to the screen, followed by a new line. It can accept formatted strings using placeholders `{}`. -- `print!`: Similar to `println!`, but prints inline without a new line. +```cairo +// This is a single-line comment +``` -Both macros use the `Display` trait for formatting. If you need to print custom data types, you must implement the `Display` trait for them. +### Multi-line Comments -Example of using `println!`: +Each line must start with `//`. ```cairo -#[executable] -fn main() { - let a = 10; - let b = 20; - let c = 30; +// This is a +// multi-line comment +``` - println!("Hello world!"); - println!("{} {} {}", a, b, c); // 10 20 30 - println!("{c} {a} {}", b); // 30 10 20 +Comments can also appear at the end of a line of code: + +```cairo +fn main() -> felt252 { + 1 + 4 // return the sum of 1 and 4 } ``` -The `format!` macro is used for string formatting without printing directly. It returns a `ByteArray` containing the formatted string. This is useful for string concatenation and can be more readable than using the `+` operator. +### Item-level Documentation -Example of using `format!`: +Prefixed with `///`, these comments document specific items like functions or traits. They can include descriptions, usage examples, and panic conditions. -```cairo -#[executable] -fn main() { - let s1: ByteArray = "tic"; - let s2: ByteArray = "tac"; - let s3: ByteArray = "toe"; - let s = format!("{s1}-{s2}-{s3}"); - let s = format!("{}-{}-{}", s1, s2, s3); - - println!("{}", s); +````cairo +/// Returns the sum of `arg1` and `arg2`. +/// `arg1` cannot be zero. +/// +/// # Panics +/// +/// This function will panic if `arg1` is `0`. +/// +/// # Examples +/// +/// ``` +/// let a: felt252 = 2; +/// let b: felt252 = 3; +/// let c: felt252 = add(a, b); +/// assert!(c == a + b, "Should equal a + b"); +/// ``` +fn add(arg1: felt252, arg2: felt252) -> felt252 { + assert!(arg1 != 0, "Cannot be zero"); + arg1 + arg2 } -``` +```` -When printing custom data types that do not implement `Display`, you will encounter an error. You can resolve this by manually implementing the `Display` trait or by using the `Debug` trait for debugging purposes. +## Common Programming Patterns and Potential Vulnerabilities -Core Cairo Programming Concepts +Certain programming patterns can lead to unintended behavior if not handled carefully. -# Core Cairo Programming Concepts +### Operator Precedence in Expressions -Cairo programs are built using variables, basic types, functions, comments, and control flow. Understanding these fundamental concepts is crucial for writing Cairo code. +Ensure expressions involving `&&` and `||` are properly parenthesized to control precedence. -## Variables and Mutability +```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" +); -Cairo variables are immutable by default, meaning their value cannot be changed after being bound. This immutability is a core aspect of Cairo's memory model, which prevents certain classes of bugs by ensuring values don't change unexpectedly. To make a variable mutable, the `mut` keyword must be used. +// ✅ fixed +assert!( + (mode == Mode::None || mode == Mode::Recovery) && (ctx.coll_ok && ctx.debt_ok), + "EMERGENCY_MODE" +); +``` -Attempting to reassign a value to an immutable variable results in a compile-time error: +### Unsigned Loop Underflow -```cairo,does_not_compile -#[executable] -fn main() { - let x = 5; - println!("The value of x is: {}", x); - x = 6; - println!("The value of x is: {}", x); -} +Using unsigned integers (`u32`) for loop counters can lead to underflow panics if decremented below zero. Use signed integers (`i32`) for counters that might handle negative values. +```cairo +// ✅ 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; +} ``` -When `mut` is used, the variable can be reassigned: +### Bit-packing into `felt252` + +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 -#[executable] -fn main() { - let mut x = 5; - println!("The value of x is: {}", x); - x = 6; - println!("The value of x is: {}", x); +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") } ``` -## Constants +## Hashing -Constants are similar to immutable variables but have key differences: +Cairo supports hashing using Pedersen and Poseidon hash functions. -- They are declared using the `const` keyword. -- The type of the value must always be annotated. -- They can only be declared in the global scope. -- They can only be set to a constant expression, not a value computed at runtime. +### Initialization and Usage -Cairo's naming convention for constants is all uppercase with underscores. +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`. -```cairo,noplayground -struct AnyStruct { - a: u256, - b: u32, -} +### Poseidon Hash Example -enum AnyEnum { - A: felt252, - B: (usize, u256), +```cairo +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; + +#[derive(Drop, Hash)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } -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]; +#[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 +} ``` -## Shadowing +### Pedersen Hash Example -Shadowing occurs when a new variable is declared with the same name as a previous one, effectively hiding the original variable. This is done using the `let` keyword again. +Pedersen hashing requires an initial base state. ```cairo -#[executable] -fn main() { - 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); +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::pedersen::PedersenTrait; + +#[derive(Drop, Hash, Serde, Copy)] +struct StructForHash { + first: felt252, + second: felt252, + third: (u32, u32), + last: bool, } -``` -Shadowing differs from `mut` because it allows changing the variable's type and does not require `mut` to reassign. The compiler treats shadowing as creating a new variable. +#[executable] +fn main() -> (felt252, felt252) { + let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; -## Statements and Expressions + // 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(); -- **Statements** perform actions but do not return a value. `let` bindings are statements. -- **Expressions** evaluate to a value. Mathematical operations and function calls are expressions. + 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); -A statement cannot be assigned to a variable: + for value in serialized_struct { + state = state.update(value); + } -```cairo, noplayground -#[executable] -fn main() { - let x = (let y = 6); + // hash2 is the result of hashing only the fields of the struct + let hash2 = state.finalize(); + + (hash1, hash2) } ``` -Blocks of code enclosed in curly braces can be expressions if they don't end with a semicolon: +## Printing and Formatting + +Cairo provides macros for printing and string formatting. + +- `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. ```cairo #[executable] fn main() { - let y = { - let x = 3; - x + 1 - }; + let s1: ByteArray = "tic"; + let s2: ByteArray = "tac"; + let s3: ByteArray = "toe"; + let s = s1 + "-" + s2 + "-" + s3; // Consumes s1, s2, s3 - println!("The value of y is: {}", y); + 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); } ``` -## Functions with Return Values +### Printing Custom Data Types -Functions can return values. The return type is specified after an arrow (`->`). The final expression in a function body is its return value. +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::`. -## Comments +## Range Check Builtin -Comments are used for explanations and are ignored by the compiler. +The Range Check builtin verifies that field elements fall within specific ranges, crucial for integer types and bounded arithmetic. -- Single-line comments start with `//`. -- Multi-line comments require `//` on each line. +### Variants -```cairo,noplayground -// This is a single-line comment. +- **Standard Range Check**: Verifies values in the range `[0, 2^128 - 1]`. +- **Range Check 96**: Verifies values in the range `[0, 2^96 - 1]`. -/* -This is a -multi-line comment. -*/ -``` +### Purpose and Importance -Item-level documentation comments, prefixed with `///`, provide detailed explanations for specific code items like functions, including usage examples and panic conditions. +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. -````cairo,noplayground -/// Returns the sum of `arg1` and `arg2`. -/// `arg1` cannot be zero. -/// -/// # Panics -/// -/// This function will panic if `arg1` is `0`. -/// -/// # Examples -/// -/// ``` -/// let a: felt252 = 2; -/// let b: felt252 = 3; -/// let c: felt252 = add(a, b); -/// assert(c == a + b, "Should equal a + b"); -/// ``` -fn add(arg1: felt252, arg2: felt252) -> felt252 { - assert(arg1 != 0, 'Cannot be zero'); - arg1 + arg2 -} -```` +### Cells Organization -Program Execution and Advanced Topics +The Range Check builtin uses a dedicated memory segment with the following characteristics: -# Program Execution and Advanced Topics +- **Valid values**: Field elements in the range `[0, 2^128 - 1]`. +- **Error conditions**: Values ≥ 2^128 or relocatable addresses. -To define an executable entry point for a Cairo program, use the `#[executable]` attribute on a function, typically named `main`. This function takes input and returns output. +Variables, Scope, and Mutability -## Writing the Prime-Checking Logic +# Variables, Scope, and Mutability -A sample program demonstrates checking if a number is prime using a trial division algorithm. +Cairo enforces an immutable memory model by default, meaning variables are immutable. However, the language provides mechanisms to handle mutability when needed. -Filename: src/lib.cairo +## Mutability -```cairo -/// Checks if a number is prime -/// -/// # Arguments -/// -/// * `n` - The number to check -/// -/// # Returns -/// -/// * `true` if the number is prime -/// * `false` if the number is not prime -fn is_prime(n: u32) -> bool { - if n <= 1 { - return false; - } - if n == 2 { - return true; - } - if n % 2 == 0 { - return false; - } - let mut i = 3; - let mut is_prime = true; - loop { - if i * i > n { - break; - } - if n % i == 0 { - is_prime = false; - break; - } - i += 2; - } - is_prime -} +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. -// Executable entry point +```cairo,does_not_compile #[executable] -fn main(input: u32) -> bool { - is_prime(input) +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); } ``` -The `is_prime` function handles edge cases (≤ 1, 2, even numbers) and iterates through odd divisors up to the square root of the input. The `main` function, marked with `#[executable]`, calls `is_prime` with user input. +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. -## Execution Flow and Memory Management - -Each instruction and its arguments increment the Program Counter (PC) by 1. The `call` and `ret` instructions manage function calls and returns, enabling a function stack. +```cairo +#[executable] +fn main() { + let mut x = 5; + println!("The value of x is: {}", x); + x = 6; + println!("The value of x is: {}", x); +} +``` -- `call rel `: Jumps to an instruction relative to the current PC. -- `ret`: Returns execution to the instruction following the `call`. +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. -Memory operations use the Allocation Pointer (`ap`). For example: +## Constants -- `[ap + 0] = value, ap++`: Stores `value` in the memory cell pointed to by `ap` and increments `ap`. -- `[ap + 0] = [ap + -1] + [ap + -2], ap++`: Reads values from memory cells relative to `ap`, performs an addition, stores the result, and increments `ap`. +Constants are similar to immutable variables but have key differences: -## Execution and Proof Generation Considerations +- 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. -The `scarb execute` command runs Cairo programs. For instance, `scarb execute -p prime_prover --print-program-output --arguments 1000001` can execute the prime checker. +Cairo's naming convention for constants is all uppercase with underscores. -Changing the data type from `u32` to `u128` allows for a larger input range. However, implementing checks, such as panicking if input exceeds a certain limit (e.g., 1,000,000), prevents proof generation for invalid inputs, as a panicked execution cannot be proven. +```cairo,noplayground +struct AnyStruct { + a: u256, + b: u32, +} -Data Types in Cairo +enum AnyEnum { + A: felt252, + B: (usize, u256), +} -Introduction to Cairo Data Types +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]; +``` -# Introduction to Cairo Data Types +Constants are useful for values that are known at compile time and used across multiple parts of the program. -Every value in Cairo is of a certain _data type_, which informs Cairo how to work with that data. Data types can be categorized into scalars and compounds. +## Shadowing -Cairo is a statically typed language, meaning that the type of each variable must be known at compile time. +Shadowing occurs when a new variable is declared with the same name as a previous variable. The new variable -Scalar Data Types (Integers, felt252, Booleans, Strings) +Data Types -# Scalar Data Types (Integers, felt252, Booleans, Strings) +# Data Types -Cairo requires all variables to have a known type at compile time. While the compiler can often infer types, explicit type annotations or conversion methods can be used when necessary. +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 #[executable] @@ -922,51 +885,37 @@ fn main() { ## Scalar Types -Scalar types represent single values. Cairo has three primary scalar types: `felt252`, integers, and booleans. +A _scalar_ type represents a single value. Cairo has three primary scalar types: `felt252`, integers, and booleans. -### Felt Type +### Felt Type (`felt252`) -The default type for variables and arguments in Cairo, if not specified, is `felt252`. This represents a field element, an integer in the range \( 0 \leq x < P \), where \( P \) is a large prime number (\( {2^{251}} + 17 \cdot {2^{192}} + 1 \)). Operations on `felt252` are performed modulo \( P \). - -Division in Cairo is defined such that \( \frac{x}{y} \cdot y = x \). If \( y \) does not divide \( x \) as integers, the result will be a value that satisfies this equation in the finite field. For example, \( \frac{1}{2} \) in Cairo is \( \frac{P+1}{2} \). +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. ### Integer Types -It is recommended to use integer types over `felt252` for added security features like overflow and underflow checks. Integers are numbers without a fractional component, and their type declaration specifies the number of bits used for storage. - -The built-in unsigned integer types in Cairo are: +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. -| Length | Unsigned | -| ------- | -------- | -| 8-bit | `u8` | -| 16-bit | `u16` | -| 32-bit | `u32` | -| 64-bit | `u64` | -| 128-bit | `u128` | -| 256-bit | `u256` | -| 32-bit | `usize` | +**Unsigned Integer Types:** -
-
Table 3-1: Integer Types in Cairo.
+| Length | Type | +| ------- | ------- | +| 8-bit | `u8` | +| 16-bit | `u16` | +| 32-bit | `u32` | +| 64-bit | `u64` | +| 128-bit | `u128` | +| 256-bit | `u256` | +| 32-bit | `usize` | -The `usize` type is currently an alias for `u32`. Unsigned integers cannot hold negative numbers; attempting to subtract a larger number from a smaller one will cause a panic. - -```cairo -fn sub_u8s(x: u8, y: u8) -> u8 { - x - y -} +`usize` is currently an alias for `u32`. Unsigned integers cannot contain negative numbers, and operations resulting in a negative value will cause a panic. -#[executable] -fn main() { - sub_u8s(1, 3); -} -``` +**Signed Integer Types:** -The `u256` type requires 4 more bits than `felt252` and is implemented as a struct: `u256 {low: u128, high: u128}`. +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. -Cairo also supports signed integers with the prefix `i` (e.g., `i8` to `i128`). A signed integer of `n` bits can represent numbers from \( -({2^{n - 1}}) \) to \( {2^{n - 1}} - 1 \). For example, `i8` ranges from -128 to 127. +**Integer Literals:** -Integer literals can be written in decimal, hexadecimal, octal, or binary formats, with optional type suffixes and underscores for readability: +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. | Numeric literals | Example | | ---------------- | --------- | @@ -975,12 +924,13 @@ Integer literals can be written in decimal, hexadecimal, octal, or binary format | Octal | `0o04321` | | Binary | `0b01` | -
-
Table 3-2: Integer Literals in Cairo.
+**`u256` Type:** + +`u256` requires 4 more bits than `felt252` and is internally represented as a struct: `u256 { low: u128, high: u128 }`. -When choosing an integer type, estimate the maximum possible value. `usize` is typically used for indexing collections. +**Numeric Operations:** -Cairo supports standard numeric operations: addition, subtraction, multiplication, division, and remainder. Integer division truncates towards zero. +Cairo supports basic arithmetic operations: addition, subtraction, multiplication, division (truncates towards zero), and remainder. ```cairo #[executable] @@ -1003,26 +953,25 @@ fn main() { } ``` -### The Boolean Type +### The Boolean Type (`bool`) -The `bool` type in Cairo has two possible values: `true` and `false`. A `bool` occupies the size of one `felt252`. Boolean variables must be initialized with `true` or `false` literals, not integer equivalents like `0` or `1`. Booleans are primarily used in control flow structures like `if` expressions. +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. ```cairo #[executable] fn main() { let t = true; - let f: bool = false; // with explicit type annotation } ``` ### String Types -Cairo does not have a built-in native string type but supports strings through two mechanisms: short strings and `ByteArray`. +Cairo handles strings using two methods: short strings with simple quotes and `ByteArray` with double quotes. #### Short strings -Short strings are ASCII strings where each character is encoded on one byte. They are represented using single quotes (`' '`) and utilize the `felt252` type. A `felt252` can store up to 31 ASCII characters (248 bits), as it is 251 bits in size. Short strings can be represented as hexadecimal values or directly as characters. +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. ```cairo # #[executable] @@ -1039,7 +988,7 @@ fn main() { #### Byte Array Strings -For strings longer than 31 characters or when byte sequence operations are needed, Cairo's Core Library provides the `ByteArray` type. It is implemented using an array of `bytes31` words and a buffer for incomplete words, abstracting the underlying memory management. `ByteArray` strings are enclosed in double quotes (`" "`). +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] @@ -1049,158 +998,95 @@ For strings longer than 31 characters or when byte sequence operations are neede # # 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 long_string: ByteArray = "this is a string which has more than 31 characters"; # } ``` -Compound Data Types (Tuples, Arrays) +## Compound Types -# Compound Data Types (Tuples, Arrays) +### The Tuple Type -## Tuples - -Tuples are a way to group multiple values of potentially different types into a single compound type. They are defined using parentheses. Each position in a tuple has a specific type. +A tuple groups together values of various types into a single compound type. Tuples have a fixed length. ```cairo #[executable] fn main() { - let tup: (u32, u64, bool) = (10, 20, true); + let tup: (u32, f64, u8) = (500, 6.4, 1); + let (x, y, z) = tup; + println!("The value of y is {}", y); } ``` -You can destructure a tuple to access its individual values: +## The `Copy` Trait -```cairo -#[executable] -fn main() { - let tup = (500, 6, true); +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. - let (x, y, z) = tup; +To implement `Copy` for a custom type, use the `#[derive(Copy)]` annotation. The type and all its components must implement `Copy`. - if y == 6 { - println!("y is 6!"); - } +```cairo,ignore_format +#[derive(Copy, Drop)] +struct Point { + x: u128, + y: u128, } -``` - -You can also declare and destructure a tuple simultaneously: -```cairo #[executable] fn main() { - let (x, y): (felt252, felt252) = (2, 3); + let p1 = Point { x: 5, y: 10 }; + foo(p1); + foo(p1); } -``` -### The Unit Type `()` +fn foo(p: Point) { // do something with p +} +``` -The unit type, represented by `()`, is a tuple with no elements. It signifies that an expression returns no meaningful value. +## Serialization and Deserialization -### Refactoring with Tuples +Serialization is the process of transforming data structures into a format that can be stored or transmitted. Deserialization is the reverse process. -Tuples can be used to group related data, improving code readability. For example, grouping width and height for a rectangle: +### Data types using at most 252 bits -```cairo -#[executable] -fn main() { - let rectangle = (30, 10); // (width, height) - let area = area(rectangle); - println!("Area is {}", area); -} +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$. -fn area(dimension: (u64, u64)) -> u64 { - let (width, height) = dimension; - width * height -} -``` +### Data types using more than 252 bits -## Fixed-Size Arrays +Types like `u256`, `u512`, arrays, spans, enums, structs, tuples, and byte arrays have non-trivial serialization. -Fixed-size arrays are collections where all elements must have the same type. They are defined using square brackets, specifying the element type and the number of elements. +**Serialization of Structs:** -The syntax for an array's type is `[element_type; number_of_elements]`. +Struct serialization is determined by the order and types of its members. -```cairo -#[executable] -fn main() { - let arr1: [u64; 5] = [1, 2, 3, 4, 5]; +```cairo,noplayground +struct MyStruct { + a: u256, + b: felt252, + c: Array } ``` -Fixed-size arrays are efficient for storing data with a known, unchanging size, such as lookup tables. They differ from the dynamically sized `Array` type provided by the core library. +Serialization of `MyStruct { a: 2, b: 5, c: [1,2,3] }` results in `[2,0,5,3,1,2,3]`. -You can initialize an array with a default value for all elements: +**Serialization of Byte Arrays:** -```cairo -let a = [3; 5]; // Creates an array of 5 elements, all initialized to 3. -``` +A `ByteArray` consists of `data` (an array of 31-byte chunks) and `pending_word` (remaining bytes) with its length. -An example using an array for month names: +**Example 1 (short string):** `hello` (0x68656c6c6f) -```cairo -let months = [ - 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', - 'October', 'November', 'December', -]; +```cairo,noplayground +0, // Number of 31-byte words in the data array. +0x68656c6c6f, // Pending word +5 // Length of the pending word, in bytes ``` -Variable Declaration, Mutability, and Shadowing +## Type Conversion -# Variable Declaration, Mutability, and Shadowing +Cairo uses the `try_into` and `into` methods from the `TryInto` and `Into` traits for type conversion. -In Cairo, you can declare variables using the `let` keyword. Shadowing allows you to declare a new variable with the same name as a previous one, effectively "shadowing" the older variable. This is useful for reusing variable names, especially when changing types. +### Into -For example, you can shadow a `u64` variable with a `felt252` variable: - -```cairo -#[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); -} -``` - -However, mutability (`mut`) does not allow changing the type of a variable. Attempting to assign a value of a different type to a mutable variable will result in a compile-time error. - -```cairo,does_not_compile -#[executable] -fn main() { - let mut x: u64 = 2; - println!("The value of x is: {}", x); - x = 5_u8; // This line causes a compile-time error - println!("The value of x is: {}", x); -} -``` - -The error message indicates that the expected type (`u64`) does not match the found type (`u8`): - -```shell -$ 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; - ^^^^ - -error: could not compile `no_listing_05_mut_cant_change_type` due to previous error -error: `scarb metadata` exited with error - -``` - -This demonstrates that while shadowing allows for type changes by declaring a new variable, mutability enforces that the variable retains its original type. - -Type Conversion and Arithmetic Operations - -# Type Conversion and Arithmetic Operations - -Cairo provides generic traits for converting between types: `Into` and `TryInto`. - -## Into - -The `Into` trait is used for infallible type conversions. To perform a conversion, call `var.into()` on the source value. The target variable's type must be explicitly defined. +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. ```cairo #[executable] @@ -1212,108 +1098,56 @@ fn main() { 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(); } ``` -## TryInto +### TryInto -The `TryInto` trait is used for fallible type conversions, returning `Option`, as the target type might not fit the source value. To perform the conversion, call `var.try_into()` on the source value. The new variable's type must also be explicitly defined. +The `TryInto` trait is used for fallible conversions, returning `Option`. Call `var.try_into()` on the source value. The new variable's type must be explicitly defined. ```cairo #[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_16: u16 = my_u32.try_into().unwrap(); + let my_u8: u8 = my_16.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.' + // This will panic: + // let my_large_u8: u8 = my_large_u16.try_into().unwrap(); } ``` -Special Data Types and Concepts (u256, Range Check, Recursive Types) - -# Special Data Types and Concepts (u256, Range Check, Recursive Types) - -## Recursive Types - -Defining a recursive data type in Cairo, where a variant directly contains another value of the same type, leads to a compilation error. This is because Cairo cannot determine the fixed size required to store such a type, as it would theoretically have an "infinite size." - -For example, a `BinaryTree` defined with a `Node` variant that holds child nodes of type `BinaryTree` will fail to compile: - -```plaintext -error: Recursive type "(core::integer::u32, listing_recursive_types_wrong::BinaryTree, listing_recursive_types_wrong::BinaryTree)" has infinite size. - --> listings/ch12-advanced-features/listing_recursive_types_wrong/src/lib.cairo:6:5 - Node: (u32, BinaryTree, BinaryTree), - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: Recursive type "listing_recursive_types_wrong::BinaryTree" has infinite size. - --> listings/ch12-advanced-features/listing_recursive_types_wrong/src/lib.cairo:11:17 - let leaf1 = BinaryTree::Leaf(1); - ^^^^^^^^^^^^^^^^^^^ -``` - -## Range Check Builtin - -The Range Check builtin is crucial for verifying that field elements fall within specific bounds, which is essential for Cairo's integer types and operations. - -### Purpose and Importance - -This builtin ensures that values adhere to bounded constraints. While range checking can be implemented in pure Cairo, it is significantly less efficient. A pure Cairo implementation might require hundreds of instructions for a single check, whereas the builtin's cost is equivalent to approximately 1.5 instructions. This efficiency makes it vital for bounded arithmetic and other operations requiring value range verification. - -### Variants - -Two variants of the Range Check builtin exist: +## Debugging with `Debug` and `Display` Traits -- **Standard Range Check**: Verifies values in the range $[0, 2^{128}-1]$. -- **Range Check 96**: Verifies values in the range $[0, 2^{96}-1]$. +### `Debug` for Debugging Purposes -This section focuses on the standard variant, but the principles apply to both. - -### Cells Organization - -The Range Check builtin utilizes a dedicated memory segment with specific validation properties: - -- **Valid values**: Field elements within the range $[0, 2^{128}-1]$. -- **Error conditions**: Values greater than or equal to $2^{128}$ or relocatable addresses. - -Working with Data Types (Printing, Examples) - -# Working with Data Types (Printing, Examples) - -## Printing and Concatenating Strings - -The `write!` macro can be used to concatenate multiple strings on the same line and then print the result. +The `Debug` trait allows printing instances of a type for debugging. It is required for `assert_xx!` macros in tests. ```cairo -use core::fmt::Formatter; +#[derive(Copy, Drop, Debug)] +struct Point { + x: u8, + y: u8, +} #[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}"); - - println!("{}", formatter.buffer); // helloworld 10 20 + let p = Point { x: 1, y: 3 }; + println!("{:?}", p); } ``` -## Implementing the `Display` Trait +### `Display` for User-Facing Output -You can implement the `Display` trait for custom structs to define how they should be printed. +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. ```cairo use core::fmt::{Display, Error, Formatter}; @@ -1328,7 +1162,6 @@ 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})") } } @@ -1336,109 +1169,75 @@ impl PointDisplay of Display { #[executable] fn main() { let p = Point { x: 1, y: 3 }; - println!("{}", p); // Point: (1, 3) + println!("{}", p); // Output: Point: (1, 3) } ``` -_Note: Printing complex data types using `Display` might require additional steps. For debugging complex data types, consider using the `Debug` trait._ - -## Printing in Hexadecimal +### Print in Hexadecimal -By default, the `Display` trait prints integers in decimal. To print them in hexadecimal, use the `{:x}` notation. +Integer values can be printed in hexadecimal using the `{:x}` notation. The `LowerHex` trait is implemented for common types. -Cairo implements the `LowerHex` trait for common types like unsigned integers, `felt252`, `NonZero`, `ContractAddress`, and `ClassHash`. You can also implement `LowerHex` for custom types similarly to how the `Display` trait is implemented. +### Print Debug Traces -Functions in Cairo +This refers to using the `Debug` trait for printing detailed information, especially during debugging. -Defining and Calling Functions - -# Defining and Calling Functions - -Functions are a fundamental part of Cairo code. The `fn` keyword is used to declare new functions, and Cairo conventionally uses snake case (all lowercase with underscores separating words) for function and variable names. - -## Defining Functions +## `Default` for Default Values -A function is defined using the `fn` keyword, followed by the function name, parentheses `()`, and curly braces `{}` to enclose the function body. For example: +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]`. ```cairo -fn another_function() { - println!("Another function."); +#[derive(Default, Drop)] +struct A { + item1: felt252, + item2: u64, } -``` - -## Calling Functions -Functions can be called by using their name followed by parentheses. A function can be called from anywhere in the program as long as it is defined within a visible scope. The order of definition in the source code does not matter. +#[derive(Default, Drop, PartialEq)] +enum CaseWithDefault { + A: felt252, + B: u128, + #[default] + C: u64, +} -```cairo #[executable] fn main() { - println!("Hello, world!"); - another_function(); + let defaulted: A = Default::default(); + assert!(defaulted.item1 == 0_felt252, "item1 mismatch"); + assert!(defaulted.item2 == 0_u64, "item2 mismatch"); + + let default_case: CaseWithDefault = Default::default(); + assert!(default_case == CaseWithDefault::C(0_u64), "case mismatch"); } ``` -When the above code is executed, the output is: - -```shell -$ scarb execute - Compiling no_listing_15_functions v0.1.0 (listings/ch02-common-programming-concepts/no_listing_15_functions/Scarb.toml) - Finished `dev` profile target(s) in 3 seconds - Executing no_listing_15_functions -Hello, world! -Another function. - +## `PartialEq` for Equality Comparisons -``` +The `PartialEq` trait enables equality comparisons using the `==` and `!=` operators. -## Abstracting with Functions +Functions -Functions can be used to abstract code, making it more reusable and easier to maintain. This is particularly useful for eliminating code duplication. +# Functions -Consider a function `largest` that finds the largest number in an array of `u8` values: +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. ```cairo -fn largest(ref number_list: Array) -> u8 { - let mut largest = number_list.pop_front().unwrap(); - - while let Some(number) = number_list.pop_front() { - if number > largest { - largest = number; - } - } - - largest +fn another_function() { + println!("Another function."); } #[executable] fn main() { - 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); + println!("Hello, world!"); + another_function(); } ``` -This `largest` function takes an array `number_list` by reference and returns a `u8` value. The process of creating such a function involves: - -- Identifying duplicated code. -- Extracting the duplicated code into a function body, defining its inputs (parameters) and return values in the function signature. -- Replacing the original duplicated code with calls to the newly created function. - -Function Parameters and Return Values - -# Function Parameters and Return Values - -Functions can accept parameters, which are variables declared in the function's signature. When calling a function, concrete values called arguments are provided for these parameters. +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. ## Parameters -Parameters must have their types declared in the function signature. +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. ```cairo #[executable] @@ -1451,48 +1250,24 @@ fn another_function(x: felt252) { } ``` -Output: - -```shell -$ scarb execute - Compiling no_listing_16_single_param v0.1.0 (listings/ch02-common-programming-concepts/no_listing_16_single_param/Scarb.toml) - Finished `dev` profile target(s) in 4 seconds - Executing no_listing_16_single_param -The value of x is: 5 - - -``` - -### Multiple Parameters +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. +Multiple parameters are separated by commas: ```cairo -fn print_labeled_measurement(value: u128, unit_label: ByteArray) { - println!("The measurement is: {value}{unit_label}"); -} - #[executable] fn main() { print_labeled_measurement(5, "h"); } -``` - -Output: - -```shell -$ scarb execute - Compiling no_listing_17_multiple_params v0.1.0 (listings/ch02-common-programming-concepts/no_listing_17_multiple_params/Scarb.toml) - Finished `dev` profile target(s) in 5 seconds - Executing no_listing_17_multiple_params -The measurement is: 5h - +fn print_labeled_measurement(value: u128, unit_label: ByteArray) { + println!("The measurement is: {value}{unit_label}"); +} ``` ### Named Parameters -Named parameters allow specifying argument names during function calls for improved readability. The syntax is `parameter_name: value`. If a variable has the same name as the parameter, `:parameter_name` can be used. +Named parameters allow specifying argument names during function calls for improved readability: ```cairo fn foo(x: u8, y: u8) {} @@ -1508,9 +1283,29 @@ fn main() { } ``` -## Return Values +## 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. + +A code block can be an expression: + +```cairo +#[executable] +fn main() { + let y = { + let x = 3; + x + 1 + }; + + println!("The value of y is: {}", y); +} +``` + +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. + +## Functions with Return Values -Functions can return values. The return type is specified after the parameter list using `-> type`. If the last expression in a function's body does not end with a semicolon, it is implicitly returned. +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. ```cairo fn five() -> u32 { @@ -1524,19 +1319,7 @@ fn main() { } ``` -Output: - -```shell -$ scarb execute - Compiling no_listing_20_function_return_values v0.1.0 (listings/ch02-common-programming-concepts/no_listing_22_function_return_values/Scarb.toml) - Finished `dev` profile target(s) in 3 seconds - Executing no_listing_20_function_return_values -The value of x is: 5 - - -``` - -Alternatively, an explicit `return` keyword can be used. +The `five` function returns `5` as a `u32`. ```cairo #[executable] @@ -1551,17 +1334,24 @@ fn plus_one(x: u32) -> u32 { } ``` -Adding a semicolon to the last expression changes it from an expression to a statement, which would result in an error if it were the intended return value. +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 `()`. -Compile-Time Functions +```cairo,does_not_compile +#[executable] +fn main() { + let x = plus_one(5); -# Compile-Time Functions + println!("The value of x is: {}", x); +} -Functions that can be evaluated at compile time can be marked as `const` using the `const fn` syntax. This allows the function to be called from a constant context and interpreted by the compiler at compile time. +fn plus_one(x: u32) -> u32 { + x + 1; // This semicolon causes an error +} +``` -Declaring a function as `const` restricts the types that arguments and the return type may use, and limits the function body to constant expressions. +### Const Functions -Several functions in the core library are marked as `const`. Here's an example from the core library showing the `pow` function implemented as a `const fn`: +Functions evaluable at compile time can be declared with `const fn`. This restricts the types and expressions allowed within the function body. ```cairo use core::num::traits::Pow; @@ -1576,294 +1366,134 @@ fn main() { } ``` -In this example, `pow` is a `const` function, allowing it to be used in a constant expression to define `mask` at compile time. Here's a snippet of how `pow` is defined in the core library using `const fn`: - -Note that declaring a function as `const` has no effect on existing uses; it only imposes restrictions for constant contexts. +The `pow` function, marked as `const`, can be used in constant expressions. -Code Reusability and Internal Functions +Control Flow -# Code Reusability and Internal Functions +# Control Flow -Functions not marked with `#[external(v0)]` or within an `#[abi(embed_v0)]` block are considered private (or internal) and can only be called from within the same contract. +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. -These internal functions can be organized in two ways: +## `if` Expressions -1. **Grouped in a dedicated `impl` block:** This allows for easy importing of internal functions into embedding contracts. -2. **Added as free functions** within the contract module. +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.” -Both methods are equivalent, and the choice depends on code readability and usability. +```cairo +#[executable] +fn main() { + let number = 3; -```cairo,noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# pub trait INameRegistry { -# fn store_name(ref self: TContractState, name: felt252); -# fn get_name(self: @TContractState, address: ContractAddress) -> felt252; -# } -# -# #[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, -# } -# -# #[derive(Drop, Serde, starknet::Store)] -# pub struct Person { -# address: ContractAddress, -# name: felt252, -# } -# -# #[constructor] -# fn constructor(ref self: ContractState, owner: Person) { -# self.names.entry(owner.address).write(owner.name); -# self.total_names.write(1); -# } -# -# // Public functions inside an impl block -# #[abi(embed_v0)] -# 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_name(self: @ContractState, address: ContractAddress) -> felt252 { -# self.names.entry(address).read() -# } -# } -# -# // Standalone public function -# #[external(v0)] -# fn get_contract_name(self: @ContractState) -> felt252 { -# 'Name Registry' -# } -# - // 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) { - let total_names = self.total_names.read(); + if number == 5 { + println!("condition was true and number = {}", number); + } else { + println!("condition was false and number = {}", number); + } +} +``` - self.names.entry(user).write(name); +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. - self.total_names.write(total_names + 1); - } - } +```cairo +#[executable] +fn main() { + let number = 3; - // Free function - fn get_total_names_storage_address(self: @ContractState) -> felt252 { - self.total_names.__base_address__ + if number != 0 { + println!("number was something other than zero"); } -# } -# +} ``` -Function Execution Details +### Handling Multiple Conditions with `else if` -# Function Execution Details +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`. -### Function Calls and Execution Flow - -The `function_call` libfunc is used to execute functions. When a function is called, its code is executed, and return values are stored. The execution flow can be affected by whether a function is inlined or not. - -For example, calling a function `not_inlined` might involve: +```cairo +#[executable] +fn main() { + let number = 3; -```cairo,noplayground -09 felt252_const<2>() -> ([0]) -10 store_temp([0]) -> ([0]) + if number == 12 { + println!("number is 12"); + } else if number == 3 { + println!("number is 3"); + } else if number - 2 == 1 { + println!("number minus 2 is 1"); + } else { + println!("number not found"); + } +} ``` -This code uses `felt252_const<2>` to get the value 2 and `store_temp` to store it. - -### Inlined vs. Non-Inlined Functions +### Using `if` in a `let` Statement -Inlined functions have their code directly inserted into the calling function's execution path. This can affect variable IDs if a variable with the same ID already exists. +Because `if` is an expression, it can be used on the right side of a `let` statement to assign the outcome to a variable. -Consider the Sierra statements for an `inlined` function: +```cairo +#[executable] +fn main() { + let condition = true; + let number = if condition { + 5 + } else { + 6 + }; -```cairo,noplayground -01 felt252_const<1>() -> ([1]) -02 store_temp([1]) -> ([1]) + if number == 5 { + println!("condition was true and number is {}", number); + } +} ``` -Here, the value 1 is stored in variable `[1]` because `[0]` might already be in use. +## Repetition with Loops -The return values of called functions are not executed prematurely. Instead, they are processed, and the final result is returned. For instance, adding values from variables `[0]` and `[1]`: +Cairo provides `loop`, `while`, and `for` loops for executing code blocks repeatedly. -```cairo,noplayground -03 felt252_add([1], [0]) -> ([2]) -04 store_temp([2]) -> ([2]) -05 return([2]) -``` - -### Casm Code Example - -The following Casm code illustrates the execution of a program involving function calls: - -```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 -``` - -This code demonstrates `call` and `ret` instructions, along with memory operations using `ap++` for storing values. - -Cairo Functions Quiz - -# Cairo Functions Quiz - -The keyword for declaring a new function in Cairo is: `fn` - -A function must declare the types of its parameters. For example, function `f` could be corrected by adding `u8` type to the `x` parameter like this: `fn f(x:u8)`. - -In Cairo, a curly-brace block like `{ /* ... */ }` is: - -1. An expression -2. A statement -3. A syntactic scope - -The following program compiles and prints `3`: - -```rust -fn f(x: usize) -> usize { x + 1 } -fn main() { - println!("{}", f({ - let y = 1; - y + 1 - })); -} -``` - -Control Flow in Cairo - -Conditional Logic (`if` expressions) - -# Conditional Logic (`if` expressions) - -An `if` expression in Cairo allows for branching code execution based on a condition. The syntax involves the `if` keyword followed by a boolean condition, and a block of code to execute if the condition is true. An optional `else` block can be provided for execution when the condition is false. - -```cairo -#[executable] -fn main() { - let number = 3; - - if number == 5 { - println!("condition was true and number = {}", number); - } else { - println!("condition was false and number = {}", number); - } -} -``` - -Cairo strictly requires conditions to be of type `bool`. Unlike some other languages, it does not automatically convert non-boolean types to booleans. For example, an `if` condition cannot be a numeric literal like `3`. - -```cairo -#[executable] -fn main() { - let number = 3; - - if number != 0 { - println!("number was something other than zero"); - } -} -``` - -## Handling Multiple Conditions with `else if` - -Multiple conditions can be handled by chaining `if` and `else` into `else if` expressions. The code block corresponding to the first true condition is executed. - -```cairo -#[executable] -fn main() { - let number = 3; - - if number == 12 { - println!("number is 12"); - } else if number == 3 { - println!("number is 3"); - } else if number - 2 == 1 { - println!("number minus 2 is 1"); - } else { - println!("number not found"); - } -} -``` - -## Using `if` in a `let` Statement - -Since `if` is an expression in Cairo, its result can be assigned to a variable using a `let` statement. Both the `if` and `else` blocks must return the same type. - -```cairo -#[executable] -fn main() { - let condition = true; - let number = if condition { - 5 - } else { - 6 - }; - - if number == 5 { - println!("condition was true and number is {}", number); - } -} -``` - -Looping Constructs (`loop`, `while`, `for`) - -# Looping Constructs (`loop`, `while`, `for`) - -Cairo provides several ways to execute code repeatedly. +### Repeating Code with `loop` -## The `loop` Construct - -The `loop` keyword executes a block of code indefinitely until explicitly stopped. You can interrupt a `loop` using `ctrl-c` or by using the `break` keyword within the 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. ```cairo #[executable] fn main() { + let mut i: usize = 0; loop { - println!("again!"); + if i > 10 { + break; + } + if i == 5 { + i += 1; + continue; + } + println!("i = {}", i); + i += 1; } } ``` -Cairo's gas mechanism prevents infinite loops in practice by limiting computation. The `--available-gas` flag can be used to set a gas limit, which will stop the program if it's exceeded. This is crucial for smart contracts to prevent unbounded execution. +#### Returning Values from Loops -A `loop` can also return a value. This is achieved by placing the value after the `break` keyword. +A `loop` can return a value by specifying it after the `break` expression. ```cairo +#[executable] fn main() { let mut counter = 0; + let result = loop { - counter += 1; if counter == 10 { break counter * 2; } + counter += 1; }; + println!("The result is {}", result); } ``` -## Conditional Loops with `while` +### Conditional Loops with `while` -The `while` loop executes a block of code as long as a given condition remains true. This is useful for repeating actions until a specific state is reached. +A `while` loop runs a block of code as long as a condition remains `true`. ```cairo #[executable] @@ -1879,24 +1509,9 @@ fn main() { } ``` -While `while` loops are effective, iterating over collections using a manual index with `while` can be error-prone and less efficient due to bounds checking on each iteration. For example: - -```cairo -#[executable] -fn main() { - let a = [10, 20, 30, 40, 50].span(); - let mut index = 0; - - while index < 5 { - println!("the value is: {}", a[index]); - index += 1; - } -} -``` - -## Looping Through a Collection with `for` +### Looping Through a Collection with `for` -The `for` loop provides a more concise and safer way to iterate over the elements of a collection. +The `for` loop provides a concise and safe way to iterate over the elements of a collection. ```cairo #[executable] @@ -1909,158 +1524,139 @@ fn main() { } ``` -This approach avoids manual index management and potential errors associated with incorrect bounds checking. - -Loop Control and Iteration - -# Loop Control and Iteration - -## Breaking out of a Loop - -The `break` keyword can be used to exit a loop prematurely. +Ranges can also be used with `for` loops. ```cairo #[executable] fn main() { - let mut i: usize = 0; - loop { - if i > 10 { - break; - } - println!("i = {}", i); - i += 1; + for number in 1..4_u8 { + println!("{number}!"); } + println!("Go!!!"); } ``` -## Continuing to the Next Iteration +## Equivalence Between Loops and Recursive Functions -The `continue` keyword skips the rest of the current loop iteration and proceeds to the next one. +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. ```cairo #[executable] -fn main() { - let mut i: usize = 0; - loop { - if i > 10 { - break; - } - if i == 5 { - i += 1; - continue; - } - println!("i = {}", i); - i += 1; +fn main() -> felt252 { + recursive_function(0) +} + +fn recursive_function(mut x: felt252) -> felt252 { + if x == 2 { + x + } else { + recursive_function(x + 1) } } ``` -Executing this program will not print the value of `i` when it is equal to `5`. +Enums and Pattern Matching -## Returning Values from Loops +# Enums and Pattern Matching -A `loop` can return a value. This is achieved by placing the value after the `break` keyword. +Enums allow you to create a type that has a few possible variants. You can define enums and instantiate them with specific values. -```cairo -#[executable] -fn main() { - let mut counter = 0; +## Enum Variants with Data - let result = loop { - if counter == 10 { - break counter * 2; - } - counter += 1; - }; +Each enum variant can hold associated data. This data can be of different types for different variants. - println!("The result is {}", result); -} +```cairo, noplayground +# #[derive(Drop)] +# enum Direction { +# North: u128, +# East: u128, +# South: u128, +# West: u128, +# } +# +# #[executable] +# fn main() { + let direction = Direction::North(10); +# } ``` -Loop Compilation and Recursion - -Loops and recursive functions are fundamental control flow mechanisms in Cairo, allowing for code repetition. +### Enums Combined with Custom Types -### Using `Range` for Iteration +Enums can store custom data types, including tuples or other structs/enums. -The `for` loop is generally preferred for its safety and conciseness. The `Range` type from the core library generates numbers in a sequence, making iteration straightforward. - -```cairo -#[executable] -fn main() { - for number in 1..4_u8 { - println!("{number}!"); - } - println!("Go!!!"); +```cairo, noplayground +#[derive(Drop)] +enum Message { + Quit, + Echo: felt252, + Move: (u128, u128), } ``` -This code iterates from 1 up to (but not including) 4, printing each number. +### Trait Implementations for Enums -### Infinite Loops and `break` +Traits can be implemented for enums to define associated methods. -The `loop` keyword creates an infinite loop that can be exited using the `break` keyword. +```cairo, noplayground +trait Processing { + fn process(self: Message); +} -```cairo -#[executable] -fn main() -> felt252 { - let mut x: felt252 = 0; - loop { - if x == 2 { - break; - } else { - x += 1; +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) }, } } - x } ``` -In this example, the loop continues incrementing `x` until it equals 2, at which point it breaks and returns `x`. - -### Equivalence Between Loops and Recursive Functions +## The `Option` Enum -Loops and recursive functions are conceptually interchangeable. A loop can be transformed into a recursive function by having the function call itself. - -```cairo -#[executable] -fn main() -> felt252 { - recursive_function(0) -} +The `Option` enum represents an optional value, with variants `Some(T)` and `None`. -fn recursive_function(mut x: felt252) -> felt252 { - if x == 2 { - x - } else { - recursive_function(x + 1) - } +```cairo,noplayground +enum Option { + Some: T, + None, } ``` -This recursive function achieves the same result as the `loop` example, incrementing `x` until it reaches 2. +It helps prevent bugs by explicitly handling the absence of a value. -### Compilation to Sierra - -In Cairo, loops and recursive functions are compiled into very similar low-level representations in Sierra. To observe this, you can enable Sierra text output in your `Scarb.toml`: - -```toml -[lib] -sierra-text = true -``` +### Example: Finding a Value -After running `scarb build`, the generated Sierra code for equivalent loop and recursive function examples will show striking similarities, indicating that the compiler optimizes loops into recursive function calls at the Sierra level. +Functions can return `Option` to indicate success or failure. -Pattern Matching (`match`, `if let`, `while let`) +```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; }, + } -# Pattern Matching (`match`, `if let`, `while let`) + find_value_recursive(arr, value, index + 1) +} -Cairo offers powerful control flow constructs for pattern matching, enabling concise and expressive code. +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 +## The `Match` Control Flow Construct -The `match` expression allows you to compare a value against a series of patterns and execute code based on the first matching pattern. The compiler enforces that all possible cases are handled, ensuring completeness. +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. -### Example: Matching an Enum +### Basic `match` Example ```cairo,noplayground enum Coin { @@ -2080,2137 +1676,1922 @@ fn value_in_cents(coin: Coin) -> felt252 { } ``` -### Matching Multiple Patterns +### Patterns That Bind to Values -Multiple patterns can be combined using the `|` operator: +`match` arms can bind to parts of values, allowing extraction of data from enum variants. ```cairo,noplayground -fn vending_machine_accept(coin: Coin) -> bool { - match coin { - Coin::Dime | Coin::Quarter => true, - _ => false, - } -} -``` -### Matching Tuples - -Tuples can be matched by specifying patterns for each element: +#[derive(Drop, Debug)] // Debug so we can inspect the state in a minute +enum UsState { + Alabama, + Alaska, +} -```cairo,noplayground #[derive(Drop)] -enum DayType { - Week, - Weekend, - Holiday, +enum Coin { + Penny, + Nickel, + Dime, + Quarter: UsState, } -fn vending_machine_accept(c: (DayType, Coin)) -> bool { - match c { - (DayType::Week, _) => true, - (_, Coin::Dime) | (_, Coin::Quarter) => true, - (_, _) => false, +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 + }, } } ``` -The wildcard `_` can be used to ignore specific tuple elements. +### Matching with `Option` -### Matching `felt252` and Integer Variables +`match` can be used to handle `Option` variants. + +```cairo +fn plus_one(x: Option) -> Option { + match x { + Some(val) => Some(val + 1), + None => None, + } +} + +#[executable] +fn main() { + let five: Option = Some(5); + let six: Option = plus_one(five); + let none = plus_one(None); +} +``` -You can match against `felt252` and integer variables, useful for ranges. Restrictions apply: only integers fitting into a single `felt252` are supported, the first arm must be 0, and arms must cover sequential segments contiguously. +### Matches Are Exhaustive + +Cairo requires `match` patterns to cover all possible cases, preventing bugs like unhandled `None` values. ```cairo,noplayground -fn roll(value: u8) { - match value { - 0 | 1 | 2 => println!("you won!"), - 3 => println!("you can roll again!"), - _ => println!("you lost..."), +fn plus_one(x: Option) -> Option { + match x { + Some(val) => Some(val + 1), } } ``` -_Note: These restrictions are planned to be relaxed in future Cairo versions._ +This code will produce a compilation error because the `None` case is not handled. -## Concise Control Flow with `if let` +### Catch-all with the `_` Placeholder -The `if let` syntax provides a more concise way to handle values that match a single pattern, ignoring others. It's syntactic sugar for a `match` that executes code for one pattern and ignores the rest. +The `_` pattern matches any value without binding it, useful for default actions. -### Example: Handling `Some` Variant - -```cairo -# #[executable] -# fn main() { - let number = Some(5); - if let Some(max) = number { - println!("The maximum is configured to be {}", max); +```cairo,noplayground +fn vending_machine_accept(coin: Coin) -> bool { + match coin { + Coin::Dime => true, + _ => false, } -# } +} ``` -This is less verbose than a `match` that requires a catch-all `_` arm. `if let` can also include an `else` block for non-matching cases. +### Multiple Patterns with the `|` Operator -```cairo -# #[derive(Drop)] -# enum Coin { -# Penny, -# Nickel, -# Dime, -# Quarter, -# } -# -# #[executable] -# fn main() { - let coin = Coin::Quarter; - let mut count = 0; - if let Coin::Quarter = coin { - println!("You got a quarter!"); - } else { - count += 1; +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, } -# println!("{}", count); -# } +} ``` -## `while let` for Looping +### Matching Tuples -The `while let` syntax allows looping over a collection of values, executing a block of code for each value that matches a specified pattern. +Tuples can be matched, allowing complex pattern matching. -### Example: Popping from a Collection +```cairo,noplayground +#[derive(Drop)] +enum DayType { + Week, + Weekend, + Holiday, +} -```cairo -#[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; +fn vending_machine_accept(c: (DayType, Coin)) -> bool { + match c { + (DayType::Week, _) => true, + (_, Coin::Dime) | (_, Coin::Quarter) => true, + (_, _) => false, } - println!("{}", sum); } ``` -This offers a more concise and idiomatic way to loop compared to traditional `while` loops with explicit `Option` handling. However, like `if let`, it sacrifices the exhaustive checking of `match`. - -Review and Project Organization +```cairo,noplayground +fn vending_week_machine(c: (DayType, Coin)) -> bool { + match c { + (DayType::Week, Coin::Quarter) => true, + _ => false, + } +} +``` -# Review and Project Organization +### Matching `felt252` and Integer Variables -Collections in Cairo +`felt252` and integer variables can be matched, with restrictions on ranges and sequential coverage. The first arm must be 0. -Introduction to Cairo Arrays +Traits and Generic Programming -# Introduction to Cairo Arrays +# Traits and Generic Programming -An array in Cairo is a collection of elements of the same type. It can be used by leveraging the `ArrayTrait` from the core library. Arrays in Cairo function as queues, meaning their values cannot be modified after creation. Elements can only be appended to the end and removed from the front, due to the immutability of memory slots once written. +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. -## Creating an Array +## Removing Duplication with Functions and Generics -Arrays are instantiated using `ArrayTrait::new()`. You can optionally specify the type of elements the array will hold during instantiation or by defining the variable type. +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. -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(0); - a.append(1); - a.append(2); -} -``` +## Trait Bounds -Explicit type definition examples: +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. -```cairo, noplayground -let mut arr = ArrayTrait::::new(); -``` +### Anonymous Generic Implementation Parameters -```cairo, noplayground -let mut arr:Array = ArrayTrait::new(); -``` +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`. -Array Creation and Structure +## Structs with Generic Types -# Array Creation and Structure +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`. -## `array!` Macro +## Generic Methods -The `array!` macro simplifies the creation of arrays with known values at compile time. It expands to code that appends items sequentially, reducing verbosity compared to manually declaring and appending. +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. -**Without `array!`:** +## Defining a Trait -```cairo - let mut arr = ArrayTrait::new(); - arr.append(1); - arr.append(2); - arr.append(3); - arr.append(4); - arr.append(5); -``` +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. -**With `array!`:** +### Generic Traits -```cairo - let arr = array![1, 2, 3, 4, 5]; -``` +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. -## Storing Multiple Types with Enums +## Implementing a Trait on a Type -To store elements of different types within an array, you can define a custom data type using an `Enum`. +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. -```cairo -#[derive(Copy, Drop)] -enum Data { - Integer: u128, - Felt: felt252, - Tuple: (u32, u32), -} +### Default Implementations -#[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))); -} -``` +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. -## Span +## Associated Items -A `Span` represents a snapshot of an `Array`, providing safe, read-only access to its elements without modifying the original array. This is useful for data integrity and avoiding borrowing issues. All `Array` methods, except `append()`, can be used with `Span`. +Associated items are definitions tied to a trait, including associated types and associated constants. -Array Operations and Manipulation +### Associated Types -# Array Operations and Manipulation +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. -## Adding Elements +### Associated Constants -Elements can be added to the end of an array using the `append()` method. +Associated constants are constants defined within a trait and implemented for specific types. They provide a way to bind constant values to traits, ensuring consistency and improving code organization. For instance, a `Shape` trait could have an associated constant `SIDES`. -```cairo -# #[executable] -# fn main() { -# let mut a = ArrayTrait::new(); -# a.append(0); - a.append(1); -# a.append(2); -# } -``` +### Associated Implementations -## Removing Elements +Associated implementations allow declaring that a trait implementation must exist for an associated type, enforcing relationships between types and implementations at the trait level. -Elements can only be removed from the front of an array using the `pop_front()` method. This method returns an `Option` which can be unwrapped to get the removed element, or `None` if the array is empty. +Ownership, Memory, and References -```cairo -#[executable] -fn main() { - let mut a = ArrayTrait::new(); - a.append(10); - a.append(1); - a.append(2); +# Ownership, Memory, and References - let first_value = a.pop_front().unwrap(); - println!("The first value is {}", first_value); -} -``` +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. -The above code will print `The first value is 10`. +## Cairo's Ownership System -Cairo's memory immutability means elements cannot be modified in place. Operations like `append` and `pop_front` work by updating pointers, not by mutating memory cells. +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: -## Reading Elements from an Array +- Ensuring code is provable and verifiable. +- Abstracting the immutable memory of the Cairo VM. -Array elements can be accessed using the `get()` or `at()` methods. `arr.at(index)` is equivalent to `arr[index]`. +### Moving Values -### `get()` Method +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. -The `get()` method returns an `Option>`. It returns a snapshot of the element at the specified index if it exists, otherwise `None`. This is useful for handling potential out-of-bounds access gracefully. +```cairo +// Example of moving an array +fn foo(mut arr: Array) { + arr.pop_front(); +} -### `set()` Method +#[executable] +fn main() { + let arr: Array = array![]; + // foo(arr); // This line would cause a compile error if uncommented, as 'arr' is moved + // foo(arr); // This second call would also fail +} +``` -The `set()` method allows updating a value at a specific index. It asserts that the index is within the array's bounds before performing the update. +The compilation error for attempting to move a value twice highlights the need for the `Copy` trait: -```cairo,noplayground -# -# 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(); -# } -# } -# -# impl MemoryVecImpl, +Copy> of MemoryVecTrait, T> { -# fn new() -> MemoryVec { -# MemoryVec { data: Default::default(), len: 0 } -# } -# -# fn get(ref self: MemoryVec, index: usize) -> Option { -# if index < self.len() { -# Some(self.data.get(index.into()).deref()) -# } else { -# None -# } -# } -# -# fn at(ref self: MemoryVec, index: usize) -> T { -# assert!(index < self.len(), "Index out of bounds"); -# self.data.get(index.into()).deref() -# } -# -# 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 -# } -# } -# -# -``` +```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(); + ^^^^^^^^^^^^^^^ -Accessing Array Elements and Safety +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::>. -# Accessing Array Elements and Safety +error: could not compile `no_listing_02_pass_array_by_value` due to previous error +error: `scarb` command exited with error +``` -## Fixed-Size Arrays +### Value Destruction -Fixed-size arrays store their elements contiguously in the program bytecode. Accessing elements is efficient. There are two primary ways to access elements: +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. -### Deconstruction +#### No-op Destruction: The `Drop` Trait -Similar to tuples, fixed-size arrays can be deconstructed into individual variables. +Types implementing the `Drop` trait are automatically destroyed when they go out of scope. This destruction is a no-op, simply indicating to the compiler that the type can be safely discarded. ```cairo +#[derive(Drop)] +struct A {} + #[executable] fn main() { - 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 + A {}; // No error, 'A' is automatically dropped. } ``` -### Using `Span` and Indexing - -Converting a fixed-size array to a `Span` allows for indexing. This conversion is free. - -```cairo -#[executable] -fn main() { - let my_arr = [1, 2, 3, 4, 5]; +Without `#[derive(Drop)]`, attempting to let a type go out of scope without explicit destruction would result in a compile error. - // Accessing elements of a fixed-size array by index - let my_span = my_arr.span(); - println!("my_span[2]: {}", my_span[2]); // my_span[2]: 3 -} -``` +#### Destruction with Side-effects: The `Destruct` Trait -Calling `.span()` once and reusing the `Span` is recommended for repeated accesses. +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. -## Dynamic Arrays +### References and Snapshots -Dynamic arrays offer methods for accessing elements, with different safety guarantees. +References allow data to be accessed without copying, improving performance by passing pointers instead of entire data structures. -### `get()` Method +#### Using Boxes for Performance -The `get()` method returns an `Option>`, allowing for safe access that handles out-of-bounds indices by returning `None`. +`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 -#[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 // 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") }, - } +#[derive(Drop)] +struct Cart { + paid: bool, + items: u256, + buyer: ByteArray, } -``` -### `at()` Method and Subscripting Operator +fn pass_data(cart: Cart) { + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); +} -The `at()` method and the subscripting operator (`[]`) provide direct access to array elements. They return a snapshot to the element, which can be dereferenced using `unbox()`. If the index is out of bounds, these methods cause a panic. Use them when out-of-bounds access should halt execution. +fn pass_pointer(cart: Box) { + let cart = cart.unbox(); + println!("{} is shopping today and bought {} items", cart.buyer, cart.items); +} -```cairo #[executable] fn main() { - let mut a = ArrayTrait::new(); - a.append(0); - a.append(1); + let new_struct = Cart { paid: true, items: 1, buyer: "Eli" }; + pass_data(new_struct); - // using the `at()` method - let first = *a.at(0); - assert!(first == 0); - // using the subscripting operator - let second = *a[1]; - assert!(second == 1); + let new_box = BoxTrait::new(Cart { paid: false, items: 2, buyer: "Uri" }); + pass_pointer(new_box); } ``` -In summary: - -- Use `get()` for safe access where out-of-bounds conditions are handled gracefully. -- Use `at()` or the subscripting operator (`[]`) when a panic is desired for out-of-bounds access. +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. -## Size-related Methods +**Note on References vs. Snapshots:** -The `len()` method returns the number of elements in an array as a `usize`. +- 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. -Testing and Verification +When attempting to modify an array passed to a function, a mutable reference (`ref`) is necessary. Snapshots are unsuitable for mutation. -# Testing and Verification +```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) +} -Dictionaries in Cairo +fn main() { + let mut arr1: Array = array![1, 2, 3]; + let elem = give_and_take(ref arr1, 4); + println!("{}", elem); // Output: 1 +} +``` -Introduction to Cairo Dictionaries +Structs, Methods, and Associated Functions -### Introduction to Cairo Dictionaries +# Structs, Methods, and Associated Functions -Cairo provides a dictionary-like data type, `Felt252Dict`, which represents a collection of unique key-value pairs. This structure is known by various names in other programming languages, such as maps, hash tables, or associative arrays. +### Defining Methods -`Felt252Dict` is particularly useful for organizing data when a simple array indexing is insufficient and for simulating mutable memory. In Cairo, the key type is restricted to `felt252`, while the value type `T` can be specified. +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. -The core operations for `Felt252Dict` are defined in the `Felt252DictTrait`, including: - -- `insert(felt252, T) -> ()`: Writes a value to the dictionary. -- `get(felt252) -> T`: Reads a value from the dictionary. +```cairo, noplayground +#[derive(Copy, Drop)] +struct Rectangle { + width: u64, + height: u64, +} -Here's an example demonstrating the basic usage of dictionaries to map individuals to their balances: +trait RectangleTrait { + fn area(self: @Rectangle) -> u64; +} -```cairo -use core::dict::Felt252Dict; +impl RectangleImpl of RectangleTrait { + fn area(self: @Rectangle) -> u64 { + (*self.width) * (*self.height) + } +} #[executable] fn main() { - let mut balances: Felt252Dict = Default::default(); + let rect1 = Rectangle { width: 30, height: 50 }; + println!("Area is {}", rect1.area()); +} +``` - balances.insert('Alex', 100); - balances.insert('Maria', 200); +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. - let alex_balance = balances.get('Alex'); - assert!(alex_balance == 100, "Balance is not 100"); +### Methods with Several Parameters - let maria_balance = balances.get('Maria'); - assert!(maria_balance == 200, "Balance is not 200"); +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 { + *self.width * *self.height + } + + fn scale(ref self: Rectangle, factor: u64) { + self.width *= factor; + self.height *= factor; + } + + fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { + *self.width > *other.width && *self.height > *other.height + } +} + +#[executable] +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + let rect2 = Rectangle { width: 10, height: 40 }; + let rect3 = Rectangle { width: 60, height: 45 }; + + println!("Can rect1 hold rect2? {}", rect1.can_hold(@rect2)); + println!("Can rect1 hold rect3? {}", rect1.can_hold(@rect3)); } ``` -Internal Implementation of `Felt252Dict` +### Associated Functions -# Internal Implementation of `Felt252Dict` +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. -## Memory Model and Entry List +To call an associated function, use the `::` syntax with the type name (e.g., `RectangleTrait::new(30, 50)`). -Cairo's memory system is immutable. To simulate mutability for dictionaries, `Felt252Dict` is implemented as a list of entries. Each entry records an interaction with a key-value pair. +```cairo +#[generate_trait] +impl RectangleImpl of RectangleTrait { + fn area(self: @Rectangle) -> u64 { + (*self.width) * (*self.height) + } -### `Entry` Structure + fn new(width: u64, height: u64) -> Rectangle { + Rectangle { width, height } + } -An `Entry` has three fields: + fn square(size: u64) -> Rectangle { + Rectangle { width: size, height: size } + } -- `key`: The identifier for the key-value pair. -- `previous_value`: The value held at `key` before the current operation. -- `new_value`: The new value held at `key` after the current operation. + fn avg(lhs: @Rectangle, rhs: @Rectangle) -> Rectangle { + Rectangle { + width: ((*lhs.width) + (*rhs.width)) / 2, height: ((*lhs.height) + (*rhs.height)) / 2, + } + } +} -The structure is defined as: +#[executable] +fn main() { + let rect1 = RectangleTrait::new(30, 50); + let rect2 = RectangleTrait::square(10); -```cairo,noplayground -struct Entry { - key: felt252, - previous_value: T, - new_value: T, + println!( + "The average Rectangle of {:?} and {:?} is {:?}", + @rect1, + @rect2, + RectangleTrait::avg(@rect1, @rect2), + ); } ``` -## Operation Logging +### 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. -Every interaction with a `Felt252Dict` registers a new `Entry`: +```cairo +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, +} -- **`get`**: Registers an entry where `previous_value` and `new_value` are the same, indicating no state change. -- **`insert`**: Registers an entry where `new_value` is the inserted element. `previous_value` is the last value for that key; if it's the first entry for the key, `previous_value` is zero. +#[executable] +fn main() { + let user1 = User { + email: "someone@example.com", username: "someusername123", active: true, sign_in_count: 1, + }; -This method avoids rewriting memory, instead creating new memory cells for each operation. + let user2 = User { + email: "another@example.com", + ..user1 // Uses remaining fields from user1 + }; +} +``` -### Example Log +### Field Init Shorthand -Consider the following operations: +When struct fields and function parameters share the same names, you can use shorthand to initialize the struct. ```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'); -# } +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, +} + +fn build_user_short(email: ByteArray, username: ByteArray) -> User { + User { active: true, username, email, sign_in_count: 1 } +} ``` -These operations produce the following list of entries: +### Dictionaries and the `Destruct` Trait -| key | previous | new | -| :---- | -------- | --- | -| Alex | 0 | 100 | -| Maria | 0 | 50 | -| Alex | 100 | 200 | -| Maria | 50 | 50 | +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. -When a key does not exist in the dictionary, its value defaults to zero. This is managed by the `zero_default` method from the `Felt252DictValue` trait. +```cairo +use core::dict::Felt252Dict; -Dictionary Operations and Methods +#[derive(Destruct)] +struct A { + dict: Felt252Dict, +} -# Dictionary Operations and Methods +#[executable] +fn main() { + A { dict: Default::default() }; // Compiles after deriving Destruct +} +``` -Cairo's `Felt252Dict` type provides a way to work with key-value pairs, overcoming the immutability of Cairo's memory by allowing updates to stored values. +Error Handling -## Basic Operations: `insert` and `get` +## Error Handling -You can create a new dictionary instance using `Default::default()` and manage its contents with methods like `insert` and `get`, which are defined in the `Felt252DictTrait` trait. +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`. -The `insert` method adds or updates a value associated with a key. The `get` method retrieves the value for a given key. Notably, `Felt252Dict` allows you to "rewrite" a stored value by inserting a new value for an existing key. +## Unrecoverable Errors with `panic` -Here's an example demonstrating how to insert and update values for a user's balance: +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`. -```cairo -use core::dict::Felt252Dict; +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's how to call `panic` and return the error code `2`: + +Filename: src/lib.cairo +```cairo #[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 mut data = array![2]; - // 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"); + if true { + panic(data); + } + println!("This line isn't reached"); } ``` -## Advanced Operations: `entry` and `finalize` +## Recoverable Errors with `Result` -The `entry` and `finalize` methods, also part of `Felt252DictTrait`, provide finer control over dictionary updates, allowing you to replicate internal operations. +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 `entry` Method +### The `Result` Enum -The `entry` method takes ownership of the dictionary and a key, returning a `Felt252DictEntry` and the previous value associated with the key. This prepares an entry for modification. +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. ```cairo,noplayground -fn entry(self: Felt252Dict, key: felt252) -> (Felt252DictEntry, T) nopanic +enum Result { + Ok: T, + Err: E, +} ``` -### The `finalize` Method +### The `ResultTrait` -The `finalize` method takes a `Felt252DictEntry` and a new value, then returns the updated dictionary. This effectively applies the changes to the entry. +The `ResultTrait` provides methods for interacting with `Result` values: ```cairo,noplayground -fn finalize(self: Felt252DictEntry, new_value: T) -> Felt252Dict +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; +} ``` -#### Implementing `custom_get` +- **`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. -You can implement a `get` functionality using `entry` and `finalize` by retrieving the previous value and then finalizing the entry with that same value to return it. +Consider a function that handles potential overflow during `u128` addition: ```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; +fn u128_overflowing_add(a: u128, b: u128) -> Result; +``` -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); +This function returns `Ok(sum)` if successful or `Err(overflowed_value)` if an overflow occurs. - // Store the value to return - let return_value = prev_value; +Example of converting `Result` to `Option`: - // Update the entry with `prev_value` and get back ownership of the dictionary - dict = entry.finalize(prev_value); +```cairo,noplayground +fn u128_checked_add(a: u128, b: u128) -> Option { + match u128_overflowing_add(a, b) { + Ok(r) => Some(r), + Err(r) => None, + } +} +``` - // Return the read value - return_value +Example of parsing a `felt252` to `u8`: + +```cairo,noplayground +fn parse_u8(s: felt252) -> Result { + match s.try_into() { + Some(value) => Ok(value), + None => Err('Invalid integer'), + } } ``` -#### Implementing `custom_insert` +### Propagating Errors -Similarly, `custom_insert` uses `entry` to get the entry for a key and then `finalize` to update it with a new value. If the key doesn't exist, `entry` provides a default value for `T`. +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. -```cairo,noplayground -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; +Listing 9-1 demonstrates propagating errors using a `match` expression: -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); +```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') + } +} - // Insert `entry` back in the dictionary with the updated value, - // and receive ownership of the dictionary - dict = entry.finalize(value); +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) } ``` -Storing Complex Data Types (Arrays and Structs) +Listing 9-1: A function that returns errors to the calling code using a `match` expression. + +### A Shortcut for Propagating Errors: the `?` Operator -# Storing Complex Data Types (Arrays and Structs) +The `?` operator simplifies error propagation. Listing 9-2 shows the `mutate_byte` function using the `?` operator: + +```cairo, noplayground +# // A hypothetical function that might fail +# fn parse_u8(input: felt252) -> Result { +# let input_u256: u256 = input.into(); +# if input_u256 < 256 { +# Result::Ok(input.try_into().unwrap()) +# } else { +# Result::Err('Invalid Integer') +# } +# } +# +fn mutate_byte(input: felt252) -> Result { + let input_to_u8: u8 = parse_u8(input)?; + let res = input_to_u8 - 1; + Ok(res) +} +# +# #[cfg(test)] +# mod tests { +# use super::*; +# #[test] +# fn test_function_2() { +# let number: felt252 = 258; +# match mutate_byte(number) { +# Ok(value) => println!("Result: {}", value), +# Err(e) => println!("Error: {}", e), +# } +# } +# } +``` -Dictionaries in Cairo natively support common data types like `felt252` and `bool`. However, more complex types such as arrays and structs (including `u256`) do not implement the necessary traits (like `Copy` or `zero_default`) for direct use in dictionaries. To store these types, you need to wrap them using `Nullable` and `Box`. +Listing 9-2: A function that returns errors to the calling code using the `?` operator. -## Using `Nullable` and `Box` +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. -`Nullable` is a smart pointer that can hold a value or be `null`. It uses `Box` to store the wrapped value in a dedicated `boxed_segment` memory. This allows types that don't natively support dictionary operations to be stored. +#### Where The `?` Operator Can Be Used -### Storing Arrays in Dictionaries +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`. -When storing an array, you can use `Nullable` and `Box`. For example, to store a `Span`: +Listing 9-3 demonstrates a compilation error when using `?` in a `main` function (which returns `()`): ```cairo -use core::dict::Felt252Dict; -use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; - +# //TAG: does_not_compile +# #[executable] fn main() { - // Create the dictionary - let mut d: Felt252Dict>> = Default::default(); + 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') + } +} +``` - // Create the array to insert - let a = array![8, 9, 10]; +Listing 9-3: Attempting to use the `?` in a `main` function that returns `()` won’t compile. - // Insert it as a `Span` - d.insert(0, NullableTrait::new(a.span())); +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. - // Get value back - let val = d.get(0); +### Summary - // 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(), - }; +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. - // 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"); -} -``` +Advanced Features and Patterns -### Challenges with Reading Arrays +# Advanced Features and Patterns -Directly using the `get` method to retrieve an array from a dictionary will result in a compiler error because `Array` does not implement the `Copy` trait, which `get` requires for copying the value. +Contract Development -```cairo -use core::dict::Felt252Dict; -use core::nullable::{FromNullableResult, match_nullable}; +# Contract Development -#[executable] -fn main() { - let arr = array![20, 19, 26]; - let mut dict: Felt252Dict>> = Default::default(); - dict.insert(0, NullableTrait::new(arr)); - println!("Array: {:?}", get_array_entry(ref dict, 0)); -} +## Storage Variables -fn get_array_entry(ref dict: Felt252Dict>>, index: felt252) -> Span { - let val = dict.get(0); // This will cause a compiler error - let arr = match match_nullable(val) { - FromNullableResult::Null => panic!("No value!"), - FromNullableResult::NotNull(val) => val.unbox(), - }; - arr.span() -} -``` +Storage variables are used to store persistent data on the blockchain. They are defined within a special `Storage` struct annotated with the `#[storage]` attribute. -The error message indicates the missing `Copy` implementation: +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. -```shell -error: Trait has no implementation in context: core::traits::Copy::>>. - --> listings/ch03-common-collections/no_listing_15_dict_of_array_attempt_get/src/lib.cairo:14:20 - let val = dict.get(0); // This will cause a compiler error - ^^^ -``` +## Storage Mappings -### Correctly Accessing and Modifying Arrays using `entry` +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. -To correctly read or modify arrays in a dictionary, use the `entry` method. This provides a reference without copying. +The process to access a value within a mapping, for example, a `Map`, involves the following steps: -To read an array: +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. -```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(); - // Finalize the entry to keep the (potentially modified) array in the dictionary - dict = entry.finalize(NullableTrait::new(arr)); - span -} -``` +Note that types like `ContractAddress` must be converted to a `StoragePointer` before they can be read from or written to. -Note: The array must be converted to a `Span` before finalizing the entry, as `NullableTrait::new(arr)` moves the array. +Cairo Circuits and Low-Level Operations -To modify an array (e.g., append a value): +# Cairo Circuits and Low-Level Operations -```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)); -} -``` +## Combining Circuit Elements and Gates -### Complete Example +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)`: -This example demonstrates insertion, reading, and appending to an array stored in a dictionary: +```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(); +# } +``` -```cairo -use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -use core::nullable::NullableTrait; +## Assigning Input Values -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)); -} +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: -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 +```cairo +pub enum AddInputResult { + /// All inputs have been filled. + Done: CircuitData, + /// More inputs are needed to fill the circuit instance's data. + More: CircuitInputAccumulator, } +``` -#[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)); +A `u384` value is represented as an array of four `u96`. For instance, initializing `a` to 10 and `b` to 20: - append_value(ref dict, 0, 30); +```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]); - println!("After insertion: {:?}", get_array_entry(ref dict, 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(); +# } ``` -The `Nullable` type is essential for dictionaries storing types that do not implement the `zero_default` method of the `Felt252DictValue` trait. - -Memory Management and Dictionary Squashing +## Low-Level Modular Arithmetic Operations -# Memory Management and Dictionary Squashing +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)`. -The `Felt252Dict` in Cairo is implemented by scanning the entire entry list for the most recent entry with a matching key for each read/write operation. This results in a worst-case time complexity of O(n), where n is the number of entries. The `previous_value` field is crucial for the "dictionary squashing" process, a mechanism required by the STARK proof system to verify computational integrity and adherence to Cairo's restrictions. +```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 -## Squashing Dictionaries +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(); -Dictionary squashing verifies that a `Felt252Dict` has not been tampered with by checking the coherence of dictionary access throughout program execution. The process involves iterating through all entries for a specific key in their insertion order. It confirms that the `new_value` of the i-th entry matches the `previous_value` of the (i+1)-th entry. + // 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); -For example, an entry list like: -| 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 | + // 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; -Would be reduced after squashing to: -| key | previous | new | -| :------ | :------- | :-- | -| Alex | 0 | 90 | -| Maria | 0 | 190 | -| Charles | 0 | 70 | + // 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, + ); -Any deviation from this sequence would cause squashing to fail at runtime. + // Bump the range_check96_ptr forward: 8 input words + 4 output words = 12 total + let range_check96_ptr = range_check96_ptr + 12; -## Dictionary Destruction and the `Destruct` Trait + // Return a pointer to the result, sitting in the last 4 words + return cast(range_check96_ptr - 4, UInt384*); -Dictionaries in Cairo must be squashed upon destruction to prove the sequence of accesses. To ensure this happens automatically, dictionaries implement the `Destruct` trait. This trait differs from `Drop` in that `Destruct` generates new CASM, whereas `Drop` is a no-op. For most types, `Drop` and `Destruct` are synonymous, but `Felt252Dict` actively uses `Destruct`. + // 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 -If a struct contains a `Felt252Dict` and does not implement `Destruct` (either directly or via `derive`), it cannot be dropped, leading to a compile-time error. + // No offsets needed for MulMod here + mul_offsets: +} +``` -Consider this code: +Data Structures and Collections -```cairo -use core::dict::Felt252Dict; +Arrays -struct A { - dict: Felt252Dict, -} +# Arrays -#[executable] -fn main() { - A { dict: Default::default() }; -} -``` +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. -This fails to compile with an error indicating the variable is not dropped because `A` implements neither `Drop` nor `Destruct`. +## Creating an Array -To resolve this, you can derive the `Destruct` trait: +Arrays are created using the `ArrayTrait::new()` call. You can optionally specify the type of elements the array will hold. ```cairo -use core::dict::Felt252Dict; - -#[derive(Destruct)] -struct A { - dict: Felt252Dict, -} - #[executable] fn main() { - A { dict: Default::default() }; // This now compiles + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); + a.append(2); } ``` -With `#[derive(Destruct)]`, the dictionary is automatically squashed when `A` goes out of scope, allowing the program to compile successfully. - -Interactive Quizzes +To explicitly define the type of elements: -# Interactive Quizzes +```cairo, noplayground +let mut arr = ArrayTrait::::new(); +``` -The following code snippets are interactive quizzes to test your understanding of dictionaries in Cairo. +Or: -
+Without `array!`: -Ownership, References, and Snapshots +```cairo + let mut arr = ArrayTrait::new(); + arr.append(1); + arr.append(2); + arr.append(3); + arr.append(4); + arr.append(5); +``` -Value Movement and Resource Management +With `array!`: -# Value Movement and Resource Management +```cairo + let arr = array![1, 2, 3, 4, 5]; +``` -In Cairo, managing values and resources efficiently is crucial, especially when variables go out of scope or are passed between functions. This involves understanding ownership, value movement, and the roles of traits like `Drop`, `Destruct`, `Clone`, and `Copy`. +## Updating an Array -## Resource Management with `Drop` and `Destruct` +### Adding Elements -When variables go out of scope, their resources need to be managed. The `Drop` trait handles no-op destruction, simply indicating that a type can be safely destroyed. The `Destruct` trait is for destruction with side effects, such as squashing dictionaries to ensure provability. If a type doesn't implement `Drop`, the compiler attempts to call `destruct`. +Elements are added to the end of an array using the `append()` method. -* **`Drop` Trait:** Allows types to be automatically destroyed when they go out of scope. Deriving `Drop` is possible for most types, except those containing dictionaries. - ```cairo - #[derive(Drop)] - struct A {} - - #[executable] - fn main() { - A {}; // No error due to #[derive(Drop)] - } - ``` -* **`Destruct` Trait:** Handles destruction with side effects. For example, `Felt252Dict` must be squashed. Types containing dictionaries, like `UserDatabase`, often require a manual `Destruct` implementation. - ```cairo - // Example for UserDatabase containing a Felt252Dict - impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { - fn destruct(self: UserDatabase) nopanic { - self.balances.squash(); - } - } - ``` - -## Moving Values and Ownership - -Moving a value transfers ownership from one variable to another. The original variable becomes invalid and cannot be used further. - -* **Arrays and Movement:** Complex types like `Array` are moved when passed to functions. Attempting to use a moved value results in a compile-time error, often indicating that the `Copy` trait is missing. - ```cairo,does_not_compile - fn foo(mut arr: Array) { - arr.pop_front(); - } - - #[executable] - fn main() { - let arr: Array = array![]; - foo(arr); // arr is moved here - foo(arr); // Error: Variable was previously moved. - } - ``` -* **Return Values:** Returning values from functions also constitutes a move. - ```cairo - #[derive(Drop)] - struct A {} - - #[executable] - fn main() { - let a1 = gives_ownership(); - let a2 = A {}; - let a3 = takes_and_gives_back(a2); - } - - fn gives_ownership() -> A { - let some_a = A {}; - some_a - } - - fn takes_and_gives_back(some_a: A) -> A { - some_a - } - ``` - -## Duplicating Values with `Clone` and `Copy` - -These traits allow for creating copies of values. - -* **`Clone` Trait:** Provides the `clone` method for explicit deep copying. Deriving `Clone` calls `clone` on each component. - ```cairo - #[derive(Clone, Drop)] - struct A { - item: felt252, - } - - #[executable] - fn main() { - let first_struct = A { item: 2 }; - let second_struct = first_struct.clone(); - assert!(second_struct.item == 2, "Not equal"); - } - ``` - Arrays can also be cloned: - ```cairo - #[executable] - fn main() { - let arr1: Array = array![]; - let arr2 = arr1.clone(); // Deep copy - } - ``` -* **`Copy` Trait:** Allows values to be duplicated. Deriving `Copy` requires all parts of the type to also implement `Copy`. When a value is copied, the original remains valid. - ```cairo - #[derive(Copy, Drop)] - struct A { - item: felt252, - } - - #[executable] - fn main() { - let first_struct = A { item: 2 }; - let second_struct = first_struct; // Value is copied - assert!(second_struct.item == 2, "Not equal"); - assert!(first_struct.item == 2, "Not Equal"); // first_struct is still valid - } - ``` - -## Performance with `Box` - -Using `Box` allows passing pointers to data, which can significantly improve performance by avoiding the copying of large data structures. Instead of copying the entire data, only a pointer is passed. - -* **Passing by Pointer:** Using `Box` and `unbox()` allows function calls to operate on data via a pointer. - ```cairo - #[derive(Drop)] - struct Cart { - paid: bool, - items: u256, - buyer: ByteArray, - } +```cairo +# #[executable] +# fn main() { +# let mut a = ArrayTrait::new(); +# a.append(0); + a.append(1); +# a.append(2); +# } +``` - fn pass_pointer(cart: Box) { - let cart = cart.unbox(); - println!("{} is shopping today and bought {} items", cart.buyer, cart.items); - } +### Removing Elements - #[executable] - fn main() { - let new_box = BoxTrait::new(Cart { paid: false, items: 2, buyer: "Uri" }); - pass_pointer(new_box); - } - ``` - This is contrasted with passing by value (`Cart`), which would involve copying the entire `Cart` struct. +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. -Accessing Values: References and Snapshots +```cairo +#[executable] +fn main() { + let mut a = ArrayTrait::new(); + a.append(10); + a.append(1); + a.append(2); -# Accessing Values: References and Snapshots + let first_value = a.pop_front().unwrap(); + println!("The first value is {}", first_value); +} +``` -In Cairo, when you pass a value to a function, ownership of that value is moved. If you want to use the value again after the function call, you must return it, which can be cumbersome. To address this, Cairo offers **snapshots** and **mutable references**, which allow you to access values without taking ownership. +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. -## Snapshots +## Reading Elements from an Array -A snapshot provides an immutable view of a value at a specific point in the program's execution. It's like a look into the past, as memory cells remain unchanged. +Elements can be accessed using the `get()` or `at()` methods, or the subscripting operator `[]`. -### Creating and Using Snapshots +### `get()` Method -You create a snapshot using the `@` operator. When you pass a snapshot to a function, the function receives a copy of the snapshot, not a pointer. The original value's ownership is not affected. +The `get()` method returns an `Option>`. It returns `Some(snapshot)` if the element exists at the given index, and `None` otherwise. This is useful for handling potential out-of-bounds access gracefully. ```cairo -#[derive(Drop)] -struct Rectangle { - height: u64, - width: u64, -} - #[executable] -fn main() { - let mut rec = Rectangle { height: 3, width: 10 }; - let first_snapshot = @rec; // Take a snapshot of `rec` at this point in time - rec.height = 5; // Mutate `rec` by changing its height - let first_area = calculate_area(first_snapshot); // Calculate the area of the snapshot - let second_area = calculate_area(@rec); // Calculate the current area - println!("The area of the rectangle when the snapshot was taken is {}", first_area); - println!("The current area of the rectangle is {}", second_area); -} - -fn calculate_area(rec: @Rectangle) -> u64 { - *rec.height * *rec.width +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") }, + } } -```` - -In this example, `calculate_area` takes a snapshot (`@Rectangle`). Accessing fields of a snapshot yields snapshots of those fields, which need to be "desnapped" using `*` to get their values. This works directly for `Copy` types like `u64`. +``` -### The Desnap Operator +### `at()` Method -The `*` operator is used to convert a snapshot back into a value. This is only possible for types that implement the `Copy` trait. +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 -#[derive(Drop)] -struct Rectangle { - height: u64, - width: u64, -} - #[executable] fn main() { - let rec = Rectangle { height: 3, width: 10 }; - let area = calculate_area(@rec); - println!("Area: {}", area); -} + let mut a = ArrayTrait::new(); + a.append(0); + a.append(1); -fn calculate_area(rec: @Rectangle) -> u64 { - // 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. - *rec.height * *rec.width + // using the `at()` method + let first = *a.at(0); + assert!(first == 0); + // using the subscripting operator + let second = *a[1]; + assert!(second == 1); } ``` -### Immutability of Snapshots +## Size-related Methods -Attempting to modify a value through a snapshot results in a compilation error because snapshots are immutable views. +- `len()`: Returns the number of elements in the array (type `usize`). +- `is_empty()`: Returns `true` if the array is empty, `false` otherwise. -```cairo,does_not_compile +## Storing Multiple Types with Enums + +To store elements of different types in an array, you can define an `Enum` to represent the different possible data types. + +```cairo #[derive(Copy, Drop)] -struct Rectangle { - height: u64, - width: u64, +enum Data { + Integer: u128, + Felt: felt252, + Tuple: (u32, u32), } #[executable] fn main() { - let rec = Rectangle { height: 3, width: 10 }; - flip(@rec); -} - -fn flip(rec: @Rectangle) { - let temp = rec.height; - rec.height = rec.width; // Error: Cannot assign to immutable field - rec.width = temp; // Error: Cannot assign to immutable field + let mut messages: Array = array![]; + messages.append(Data::Integer(100)); + messages.append(Data::Felt('hello world')); + messages.append(Data::Tuple((10, 30))); } ``` -## Mutable References - -Mutable references allow you to modify a value while retaining ownership in the calling context. They are created using the `ref` keyword. +## Span -### Using Mutable References +`Span` is a struct representing a snapshot of an `Array`, providing safe, read-only access. It supports all `Array` methods except `append()`. -To use a mutable reference, the variable must be declared with `mut`, and the `ref` keyword must be used both when passing the variable to the function and in the function signature. +To create a `Span` from an `Array`, use the `span()` method: ```cairo -#[derive(Drop)] -struct Rectangle { - height: u64, - width: u64, -} - #[executable] fn main() { - let mut rec = Rectangle { height: 3, width: 10 }; - flip(ref rec); - println!("height: {}, width: {}", rec.height, rec.width); -} - -fn flip(ref rec: Rectangle) { - let temp = rec.height; - rec.height = rec.width; - rec.width = temp; + let mut array: Array = ArrayTrait::new(); + array.span(); } ``` -When a function takes a mutable reference, it operates on a local copy of the data, which is implicitly returned to the caller at the end of the function's execution. This ensures that the original variable remains valid and can be used after the function call. +Dictionaries (Felt252Dict) -Advanced Topics and Practical Applications +# Dictionaries (Felt252Dict) -# Advanced Topics and Practical Applications +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. -Structs in Cairo +## Basic Use of Dictionaries -Understanding Structs in Cairo +The `Felt252DictTrait` trait provides core dictionary operations: -# Understanding Structs in Cairo +1. `insert(felt252, T) -> ()`: Writes values to a dictionary. +2. `get(felt252) -> T`: Reads values from a dictionary. -Creating and Instantiating Structs +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. -# Creating and Instantiating Structs - -Structs are custom data types that group related values, similar to tuples but with named fields for clarity and flexibility. They are defined using the `struct` keyword, followed by the struct name and fields enclosed in curly braces. +### Example: Basic Dictionary Operations ```cairo -#[derive(Drop)] -struct User { - active: bool, - username: ByteArray, - email: ByteArray, - sign_in_count: u64, -} -``` - -Instances of structs are created by specifying values for each field using key-value pairs within curly braces. The order of fields in the instance does not need to match the definition. +use core::dict::Felt252Dict; -```cairo -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", -}; -``` +#[executable] +fn main() { + let mut balances: Felt252Dict = Default::default(); -## Using the Field Init Shorthand + balances.insert('Alex', 100); + balances.insert('Maria', 200); -When function parameters have the same names as struct fields, the field init shorthand can be used to simplify instantiation. + let alex_balance = balances.get('Alex'); + assert!(alex_balance == 100, "Balance is not 100"); -```cairo -fn build_user_short(email: ByteArray, username: ByteArray) -> User { - User { active: true, username, email, sign_in_count: 1 } + let maria_balance = balances.get('Maria'); + assert!(maria_balance == 200, "Balance is not 200"); } ``` -## Creating Instances from Other Instances with Struct Update Syntax +### Example: Updating Dictionary Values -The struct update syntax (`..`) allows creating a new instance by copying most fields from an existing instance, while specifying new values for only a few. +`Felt252Dict` allows updating values for existing keys: ```cairo -let user2 = User { email: "another@example.com", ..user1 }; -``` - -This syntax copies the remaining fields from `user1` into `user2`, making the code more concise. - -Interacting with Structs - -# Interacting with Structs - -To access a specific value from a struct instance, use dot notation. For example, to access `user1`'s email address, use `user1.email`. If the instance is mutable, you can change a field's value using dot notation and assignment. +use core::dict::Felt252Dict; -```cairo -# #[derive(Drop)] -# struct User { -# active: bool, username: ByteArray, email: ByteArray, -# sign_in_count: u64, -# } #[executable] fn main() { - let mut user1 = User { - active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, - }; - user1.email = "anotheremail@example.com"; + let 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"); } ``` -Note that the entire instance must be mutable; Cairo does not allow marking only certain fields as mutable. +## Dictionaries Underneath -## Creating New Instances +Cairo's memory is immutable. `Felt252Dict` simulates mutability by storing a list of entries. Each entry represents an access (read/write/update) and contains: -A new struct instance can be created as the last expression in a function to implicitly return it. The `build_user` function demonstrates this, initializing fields with provided values and setting defaults for others. +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. -```cairo -# #[derive(Drop)] -# struct User { -# active: bool, username: ByteArray, email: ByteArray, -# sign_in_count: u64, -# } -# #[executable] -# fn main() { -# let mut user1 = User { -# active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, -# }; -# user1.email = "anotheremail@example.com"; -# } -fn build_user(email: ByteArray, username: ByteArray) -> User { - User { active: true, username: username, email: email, sign_in_count: 1 } +An `Entry` struct is defined as: + +```cairo,noplayground +struct Entry { + key: felt252, + previous_value: T, + new_value: T, } ``` -## Struct Update Syntax +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. -The struct update syntax allows creating a new instance using values from an existing instance, specifying only the fields that differ. The `..instance_name` syntax copies the remaining fields. +### Example: Entry List Generation + +Consider the following operations: ```cairo -# #[derive(Drop)] -# struct User { -# active: bool, username: ByteArray, email: ByteArray, -# sign_in_count: u64, +# use core::dict::Felt252Dict; +# +# struct Entry { +# key: felt252, +# previous_value: T, +# new_value: T, # } +# # #[executable] # fn main() { -# let mut user1 = User { -# active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, -# }; -# user1.email = "anotheremail@example.com"; -# } -# -# fn build_user(email: ByteArray, username: ByteArray) -> User { -# User { active: true, username: username, email: email, sign_in_count: 1 } +# let mut balances: Felt252Dict = Default::default(); + balances.insert('Alex', 100_u64); + balances.insert('Maria', 50_u64); + balances.insert('Alex', 200_u64); + balances.get('Maria'); # } -# -fn build_user_short(email: ByteArray, username: ByteArray) -> User { - User { active: true, username, email, sign_in_count: 1 } -} - -# Example usage of struct update syntax: -# let user2 = User { email: String::from("another@example.com"), ..user1 }; ``` -When using struct update syntax, the original instance may become invalid if fields that do not implement the `Copy` trait (like `ByteArray`) are moved. Fields implementing `Copy` are duplicated. - -Advanced Struct Features - -# Advanced Struct Features - -Structs in Practice: Examples and Exercises - -# Structs in Practice: Examples and Exercises - -This section demonstrates the practical application of structs in Cairo through an example of a generic user database. +This produces the following entry list: -## User Database Example +| key | previous | new | +| :---: | -------- | --- | +| Alex | 0 | 100 | +| Maria | 0 | 50 | +| Alex | 100 | 200 | +| Maria | 50 | 50 | -The `UserDatabase` struct is a generic type representing a database of users, where `T` is the type of the user balances. +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". -### `UserDatabase` Struct Definition +## Squashing Dictionaries -The `UserDatabase` struct has the following members: +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. -- `users_updates`: Tracks the number of updates made to the user database. -- `balances`: A mapping from user identifiers ( `felt252`) to their balances (type `T`). +### Example: Squashing Reduction -### `UserDatabaseTrait` and Implementation +Given this entry list: -The core functionality of the `UserDatabase` is defined by the `UserDatabaseTrait`. +| 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 | -#### Defined Methods: +After squashing, it becomes: -- `new()`: Creates a new instance of `UserDatabase`. -- `update_user(name: felt252, balance: T)`: Updates a user's balance and increments the `users_updates` count. -- `get_balance(name: felt252)`: Retrieves a user's balance. +| key | previous | new | +| :-----: | -------- | --- | +| Alex | 0 | 90 | +| Maria | 0 | 190 | +| Charles | 0 | 70 | -#### Generic Type `T` Requirements: +Squashing is automatically called via the `Destruct` trait implementation when a `Felt252Dict` goes out of scope. -For `UserDatabase` to work with `Felt252Dict`, the generic type `T` must satisfy the following trait bounds: +## Entry and Finalize -1. `Copy`: Required for retrieving values from a `Felt252Dict`. -2. `Felt252DictValue`: The value type must implement this trait. -3. `Drop`: Required for inserting values into the dictionary. +The `entry` and `finalize` methods allow manual interaction with dictionary entries, mimicking internal operations. -#### Implementation Details: +- `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. -The implementation of `UserDatabaseTrait` for `UserDatabase` is shown below, incorporating the necessary trait bounds: +### Example: Custom `get` Implementation ```cairo,noplayground -impl UserDatabaseImpl> of UserDatabaseTrait { - // Creates a database - fn new() -> UserDatabase { - UserDatabase { users_updates: 0, balances: Default::default() } - } +use core::dict::{Felt252Dict, Felt252DictEntryTrait}; - // Get the user's balance - fn get_balance<+Copy>(ref self: UserDatabase, name: felt252) -> T { - self.balances.get(name) - } +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); - // Add a user - fn update_user<+Drop>(ref self: UserDatabase, name: felt252, balance: T) { - self.balances.insert(name, balance); - self.users_updates += 1; - } + // Store the value to return + let return_value = prev_value; + + // Update the entry with `prev_value` and get back ownership of the dictionary + dict = entry.finalize(prev_value); + + // Return the read value + return_value } ``` -Methods and Associated Functions +### Example: Custom `insert` Implementation -Defining Methods in Cairo +```cairo,noplayground +use core::dict::{Felt252Dict, Felt252DictEntryTrait}; -# Defining Methods in Cairo +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); -Methods in Cairo are similar to functions, declared with `fn`, and can have parameters and return values. They are defined within the context of a struct or enum, with the first parameter always being `self`, representing the instance on which the method is called. + // Insert `entry` back in the dictionary with the updated value, + // and receive ownership of the dictionary + dict = entry.finalize(value); +} +``` -## Defining Methods on Structs +## Dictionaries of Types not Supported Natively -To define methods within a struct's context, you use an `impl` block for a trait that defines the methods. The first parameter of a method is `self`, which can be taken by ownership, snapshot (`@`), or mutable reference (`ref`). +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`). -```cairo -#[derive(Copy, Drop)] -struct Rectangle { - width: u64, - height: u64, -} +To store unsupported types, wrap them in `Nullable`, which uses `Box` to manage memory in a dedicated segment. -trait RectangleTrait { - fn area(self: @Rectangle) -> u64; -} +### Example: Storing `Span` in a Dictionary -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } -} +```cairo +use core::dict::Felt252Dict; +use core::nullable::{FromNullableResult, NullableTrait, match_nullable}; #[executable] fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - println!("Area is {}", rect1.area()); -} -``` - -Method syntax is used to call methods on an instance: `instance.method_name(arguments)`. + // Create the dictionary + let mut d: Felt252Dict>> = Default::default(); -## The `#[generate_trait]` Attribute + // Create the array to insert + let a = array![8, 9, 10]; -To simplify method definition without needing to explicitly define a trait, Cairo provides the `#[generate_trait]` attribute. This attribute automatically generates the trait definition, allowing you to focus solely on the implementation. + // Insert it as a `Span` + d.insert(0, NullableTrait::new(a.span())); -```cairo -#[derive(Copy, Drop)] -struct Rectangle { - width: u64, - height: u64, -} + // Get value back + let val = d.get(0); -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } -} + // 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(), + }; -#[executable] -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - println!("Area is {}", rect1.area()); + // 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"); } ``` -## Snapshots and References +## Using Arrays inside Dictionaries -Methods can accept `self` as a snapshot (`@`) if they don't modify the instance, or as a mutable reference (`ref`) to modify it. +Storing and modifying arrays in dictionaries requires careful handling due to `Array` not implementing the `Copy` trait. -```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; - } -} +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. -#[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); +### Example: Reading an Array Entry + +```cairo,noplayground +fn get_array_entry(ref dict: Felt252Dict>>, index: felt252) -> Span { + let (entry, _arr) = dict.entry(index); + let mut arr = _arr.deref_or(array![]); + let span = arr.span(); + dict = entry.finalize(NullableTrait::new(arr)); + span } ``` -## Methods with Several Parameters +### Example: Appending to an Array in a Dictionary + +```cairo,noplayground +fn append_value(ref dict: Felt252Dict>>, index: felt252, value: u8) { + let (entry, arr) = dict.entry(index); + let mut unboxed_val = arr.deref_or(array![]); + unboxed_val.append(value); + dict = entry.finalize(NullableTrait::new(unboxed_val)); +} +``` -Methods can accept multiple parameters, including other instances of the same type. +### Complete Example: Array Manipulation in Dictionary ```cairo -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - *self.width * *self.height - } +use core::dict::{Felt252Dict, Felt252DictEntryTrait}; +use core::nullable::NullableTrait; - fn scale(ref self: Rectangle, factor: u64) { - self.width *= factor; - self.height *= factor; - } +fn append_value(ref dict: Felt252Dict>>, index: felt252, value: u8) { + let (entry, arr) = dict.entry(index); + let mut unboxed_val = arr.deref_or(array![]); + unboxed_val.append(value); + dict = entry.finalize(NullableTrait::new(unboxed_val)); +} - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { - *self.width > *other.width && *self.height > *other.height - } +fn get_array_entry(ref dict: Felt252Dict>>, index: felt252) -> Span { + let (entry, _arr) = dict.entry(index); + let mut arr = _arr.deref_or(array![]); + let span = arr.span(); + dict = entry.finalize(NullableTrait::new(arr)); + span } #[executable] fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - let rect2 = Rectangle { width: 10, height: 40 }; - let rect3 = Rectangle { width: 60, height: 45 }; + 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)); - println!("Can rect1 hold rect2? {}", rect1.can_hold(@rect2)); - println!("Can rect1 hold rect3? {}", rect1.can_hold(@rect3)); + append_value(ref dict, 0, 30); + + println!("After insertion: {:?}", get_array_entry(ref dict, 0)); } ``` -Associated Functions in Cairo - -# Associated Functions in Cairo +## Nested Mappings -Associated functions are functions defined within an `impl` block that are tied to a specific type. It's good practice to group functions related to the same type within the same `impl` block. +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. -Unlike methods, associated functions do not necessarily take `self` as their first parameter. This allows them to be called without an instance of the type, often serving as constructors or utility functions related to the type. +```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() +# } +# } +# } +``` -A common use case for associated functions that are not methods is to act as constructors. While `new` is a conventional name, it's not a reserved keyword. The following example demonstrates `new` for creating a `Rectangle`, `square` for creating a square `Rectangle`, and `avg` for calculating the average of two `Rectangle` instances: +## Storage Address Computation for Mappings -```cairo -#[generate_trait] -impl RectangleImpl of RectangleTrait { - fn area(self: @Rectangle) -> u64 { - (*self.width) * (*self.height) - } +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. - fn new(width: u64, height: u64) -> Rectangle { - Rectangle { width, height } - } +## Summary - fn square(size: u64) -> Rectangle { - Rectangle { width: size, height: size } - } +- `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. - fn avg(lhs: @Rectangle, rhs: @Rectangle) -> Rectangle { - Rectangle { - width: ((*lhs.width) + (*rhs.width)) / 2, height: ((*lhs.height) + (*rhs.height)) / 2, - } - } -} +Structs -#[executable] -fn main() { - let rect1 = RectangleTrait::new(30, 50); - let rect2 = RectangleTrait::square(10); +# Structs - println!( - "The average Rectangle of {:?} and {:?} is {:?}", - @rect1, - @rect2, - RectangleTrait::avg(@rect1, @rect2), - ); -} -``` +## Using Structs to Structure Related Data -Calling Methods and Associated Functions +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. -# Calling Methods and Associated Functions +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. -Associated functions are called using the `::` syntax with the struct name, for example, `RectangleTrait::square(3)`. This syntax is also used for namespaces created by modules. +## Defining and Instantiating Structs -Methods can be called directly on the type they are defined for. If a type implements `Deref` to another type, methods defined on the target type can be called directly on the source type instance due to deref coercion. This simplifies access to nested data structures and reduces boilerplate code. +To define a struct, we use the keyword `struct` and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Inside curly brackets, we define the names and types of the fields. -```cairo -struct MySource { - pub data: u8, +```cairo, noplayground +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } +``` -struct MyTarget { - pub data: u8, -} +You can derive multiple traits on structs, such as `Drop`, `PartialEq` for comparison, and `Debug` for debug-printing. -#[generate_trait] -impl TargetImpl of TargetTrait { - fn foo(self: MyTarget) -> u8 { - self.data - } -} +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. -impl SourceDeref of Deref { - type Target = MyTarget; - fn deref(self: MySource) -> MyTarget { - MyTarget { data: self.data } - } +```cairo +#[derive(Drop)] +struct User { + active: bool, + username: ByteArray, + email: ByteArray, + sign_in_count: u64, } #[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); + 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", + }; } ``` -Each struct can have multiple `trait` and `impl` blocks, allowing methods to be separated into different blocks, although this is not always necessary. - -Syntax and Related Topics - -# Syntax and Related Topics - -Enums and Pattern Matching +## Accessing, Mutating, and Updating Structs -Introduction to Enums +To get a specific value from a struct, we use dot notation. For example, to access `user1`'s email address, we use `user1.email`. -# Introduction to Enums +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. -No content available for this section. +```cairo +# #[derive(Drop)] +# struct User { +# active: bool, +# username: ByteArray, +# email: ByteArray, +# sign_in_count: u64, +# } +#[executable] +fn main() { + let mut user1 = User { + active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, + }; + user1.email = "anotheremail@example.com"; +} +``` -Enum Variants and Associated Data +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. -# Enum Variants and Associated Data +```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, + }; -Enums in Cairo allow variants to have associated data, enabling them to represent more complex states. + let user2 = User { email: "another@example.com", ..user1 }; +} +``` -## Defining Variants with Associated Data +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. -Variants can be defined to hold specific data types. +## Example: Structs vs. Separate Variables and Tuples -### Primitive and Tuple Data +Consider a program to calculate the area of a rectangle. Without structs, we might use separate variables. -```cairo, noplayground -#[derive(Drop)] -enum Direction { - North: u128, - East: u128, - South: u128, - West: u128, +```cairo +#[executable] +fn main() { + let width = 30; + let height = 10; + let area = area(width, height); + println!("Area is {}", area); } -#[derive(Drop)] -enum Message { - Quit, - Echo: felt252, - Move: (u128, u128), +fn area(width: u64, height: u64) -> u64 { + width * height } ``` -These variants can be instantiated with their respective data: +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. -```cairo, noplayground -let direction = Direction::North(10); -let message = Message::Echo("hello"); -let movement = Message::Move((10, 20)); -``` - -### Custom Data in Variants - -Enums can also associate custom types, such as other enums or structs, with their variants. +Refactoring with tuples can group them: -```cairo,noplayground -#[derive(Drop, Debug)] -enum UsState { - Alabama, - Alaska, +```cairo +#[executable] +fn main() { + let rectangle = (30, 10); + let area = area(rectangle); + println!("Area is {}", area); } -#[derive(Drop)] -enum Coin { - Penny, - Nickel, - Dime, - Quarter: UsState, +fn area(dimension: (u64, u64)) -> u64 { + let (x, y) = dimension; + x * y } ``` -## Using Associated Data with `match` +While better, using named fields in structs provides more clarity and flexibility than tuples for grouping related data. -The `match` control flow construct can destructure enum variants and bind their associated data to variables. +Enums -```cairo,noplayground -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 - }, - } -} -``` +# Enums -## Trait Implementations for 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. -Traits can be implemented for enums to define associated behaviors. +## Enum Variants and Values -```cairo,noplayground -trait Processing { - fn process(self: Message); -} +Here's a simple example of an enum with variants that do not have associated values: -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) }, - } - } +```cairo, noplayground +#[derive(Drop)] +enum Direction { + North, + East, + South, + West, } ``` -Common Cairo Enums (`Option`, `Result`) - -# Common Cairo Enums (`Option`, `Result`) +A variant can be instantiated using the following syntax: -## The `Option` Enum and Its Advantages +```cairo, noplayground +# #[derive(Drop)] +# enum Direction { +# North, +# East, +# South, +# West, +# } +# +# #[executable] +# fn main() { + let direction = Direction::North; +# } +``` -The `Option` enum is a standard Cairo enum that represents the concept of an optional value. It has two variants: `Some: T` and `None`. `Some: T` indicates that there's a value of type `T`, while `None` represents the absence of a value. +Variants can also have associated values. For example, to store the degree of a direction: -```cairo,noplayground -enum Option { - Some: T, - None, +```cairo, noplayground +#[derive(Drop)] +enum Direction { + North: u128, + East: u128, + South: u128, + West: u128, } +# +# #[executable] +# fn main() { +# let direction = Direction::North(10); +# } ``` -The `Option` enum is helpful because it allows you to explicitly represent the possibility of a value being absent, making your code more expressive and easier to reason about. Using `Option` can also help prevent bugs caused by using uninitialized or unexpected `null` values. +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. -Here is a function which returns the index of the first element of an array with a given value, or `None` if the element is not present, demonstrating two approaches: +## Recursive Types -- Recursive approach with `find_value_recursive`. -- Iterative approach with `find_value_iterative`. +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. -```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; }, - } +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: - find_value_recursive(arr, value, index + 1) +```cairo, noplayground +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, BinaryTree, BinaryTree), } -fn find_value_iterative(mut arr: Span, value: felt252) -> Option { - let mut result = None; - let mut index = 0; - - while let Some(array_value) = arr.pop_front() { - if (*array_value == value) { - result = Some(index); - break; - } - - index += 1; - } - - result +#[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)); } ``` -The `match` Expression - -### The `match` Expression +Smart Pointers and Memory Management -The `match` expression in Cairo is similar to a conditional expression used with `if`, but it can evaluate any type, not just booleans. It consists of the `match` keyword followed by an expression, and then arms. Each arm has a pattern and code separated by `=>`. +# Smart Pointers and Memory Management -When a `match` expression runs, it compares the value of the expression against the pattern of each arm in order. If a pattern matches the value, the associated code is executed. If a pattern does not match, execution proceeds to the next arm. The value of the expression in the matching arm becomes the return value of the entire `match` expression. +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. -For single-line expressions in an arm, curly braces are not typically used. However, if an arm requires multiple lines of code, curly braces must be used, and a comma must follow the arm. The last expression within the curly braces is the value returned for that arm. +## The `Box` Type for Pointer Manipulation -```cairo,noplayground -fn value_in_cents(coin: Coin) -> felt252 { - match coin { - Coin::Penny => { - println!("Lucky penny!"); - 1 - }, - Coin::Nickel => 5, - Coin::Dime => 10, - Coin::Quarter => 25, - } -} -``` - -`match` with Enum Variants - -### `match` with Enum Variants +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. -When using `match` with enums, you can bind the inner value of a variant to a variable. For example, if `state` is an `UsState` enum, a match arm like `Coin::Quarter(state)` will bind the inner `UsState` value to the `state` variable. This allows you to use the inner value in the match arm's code. +`Box` is useful in two main scenarios: -You can print the debug form of an enum value using the `{:?}` formatting syntax with the `println!` macro. +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. -#### Matching with `Option` +### Storing Data in the Boxed Segment with `Box` -The `match` expression can also be used to handle `Option` variants, similar to other enums. For instance, a function that adds 1 to an `Option` can be written as: +The `Box` type facilitates storing data in the boxed segment. ```cairo -fn plus_one(x: Option) -> Option { - match x { - Some(val) => Some(val + 1), - None => None, - } -} - #[executable] fn main() { - let five: Option = Some(5); - let six: Option = plus_one(five); - let none = plus_one(None); + let b = BoxTrait::new(5_u128); + println!("b = {}", b.unbox()) } ``` -In this example: +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. -- `Some(val) => Some(val + 1)`: If `x` is `Some(5)`, `val` is bound to `5`, and the arm returns `Some(5 + 1)`, which is `Some(6)`. -- `None => None`: If `x` is `None`, this arm matches, and the function returns `None`. +### Enabling Recursive Types with Boxes -#### Matches Are Exhaustive +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. -`match` expressions in Cairo must cover all possible patterns for the type being matched. If a pattern is missing, the code will not compile. For example, if a `match` on `Option` only includes the `Some(val)` arm and omits `None`, the compiler will report a "Missing match arm" error. +Consider the `BinaryTree` enum: -```cairo,noplayground -fn plus_one(x: Option) -> Option { - match x { - Some(val) => Some(val + 1), - } // Error: `None` not covered. -} -``` +```cairo +mod display; +use display::DebugBinaryTree; -The `_` placeholder can be used to ignore values or patterns that are not needed. +#[derive(Copy, Drop)] +enum BinaryTree { + Leaf: u32, + Node: (u32, Box, Box), +} -Advanced `match` Features -# Advanced `match` Features +#[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))); -Matches in Cairo are exhaustive, meaning all possibilities must be handled. This prevents errors like assuming a value exists when it might be null, avoiding the "billion-dollar mistake". + println!("{:?}", root); +} +``` -## Catch-all with the `_` Placeholder +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`. -The `_` pattern matches any value without binding to it. It's used as the last arm in a `match` expression for a default action. +### Implementing `Destruct` for Memory Management -For example, a `vending_machine_accept` function that only accepts `Coin::Dime`: +For structs containing types like `Felt252Dict`, the `Destruct` trait must be implemented to define how the struct goes out of scope. -```cairo,noplayground -fn vending_machine_accept(coin: Coin) -> bool { - match coin { - Coin::Dime => true, - _ => false, +```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(); } } ``` -This example is exhaustive because the `_` arm handles all other values. +Serialization and Iteration -## Multiple Patterns with the `|` Operator +# Serialization and Iteration -The `|` operator allows matching multiple patterns within a single `match` arm. +## Serialization of `u256` -Enums vs. Structs and Best Practices +A `u256` value in Cairo is serialized as two `felt252` values: -# Enums vs. Structs and Best Practices +- The first `felt252` contains the 128 least significant bits (low part). +- The second `felt252` contains the 128 most significant bits (high part). -There is no content available for this section. +Examples: -Modules and Packages +- `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]`. -Introduction to Cairo Modules and Packages +## Serialization of `u512` -# Introduction to Cairo Modules and Packages +The `u512` type is a struct containing four `felt252` members, each representing a 128-bit limb. -Cairo's module system helps manage code organization and scope. Key features include: +## Serialization of Arrays and Spans -- **Packages:** A Scarb feature for building, testing, and sharing crates. -- **Crates:** A compilation unit consisting of a tree of modules with a root directory and a root module (often `lib.cairo`). -- **Modules and use:** Control item organization and scope. -- **Paths:** Names used to identify items like structs, functions, or modules. +Arrays and spans are serialized as: +`, ,..., ` -## Packages and Crates +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]`. -### What is a Crate? +```cairo, noplayground +let POW_2_128: u256 = 0x100000000000000000000000000000000 +let array: Array = array![10, 20, POW_2_128] +``` -A crate is a subset of a package compiled by Cairo. It includes: +## Serialization of Enums -- The package's source code, identified by its name and crate root (the entry point). -- Package metadata for crate-level compiler settings (e.g., `edition` in `Scarb.toml`). +Enums are serialized as: +`,` -Crates can contain modules, which can be defined in separate files compiled with the crate. +Enum variant indices are 0-based. -Module Structure, Paths, and Visibility +**Example 1:** -# Module Structure, Paths, and Visibility +```cairo,noplayground +enum Week { + Sunday: (), // Index=0. + Monday: u256, // Index=1. +} +``` -Modules allow organizing code within a crate for readability and reuse, and they control item privacy. Code within a module is private by default, meaning it's only accessible by the current module and its descendants. +| Instance | Index | Type | Serialization | +| ----------------- | ----- | ------ | ------------- | +| `Week::Sunday` | `0` | unit | `[0]` | +| `Week::Monday(5)` | `1` | `u256` | `[1,5,0]` | -## Declaring Modules and Submodules +**Example 2:** -- **Crate Root**: The compiler starts by looking in the crate root file (`src/lib.cairo`). -- **Module Declaration**: Declare a module using `mod module_name;`. +```cairo,noplayground +enum MessageType { + A, + #[default] + B: u128, + C +} +``` - - The compiler looks for the module's code inline within curly braces `{}` in the same file. - - Alternatively, it looks in a file named `src/module_name.cairo` (for top-level modules) or `src/parent_module/module_name.cairo` (for submodules). +| Instance | Index | Type | Serialization | +| ------------------- | ----- | ------ | ------------- | +| `MessageType::A` | `1` | unit | `[0]` | +| `MessageType::B(6)` | `0` | `u128` | `[1,6]` | +| `MessageType::C` | `2` | unit | `[2]` | - ```cairo,noplayground - // crate root file (src/lib.cairo) - mod garden { - // code defining the garden module goes here - } - ``` +The `#[default]` attribute does not affect serialization. - ```cairo,noplayground - // src/garden.cairo file - mod vegetables { - // code defining the vegetables submodule goes here - } - ``` +## Serialization of Structs and Tuples -- **Module Tree**: Modules form a tree structure, with the crate root at the top. Siblings are modules defined within the same parent module. A parent module contains its child modules. +Structs and tuples are serialized by serializing their members sequentially in the order they appear in their definition. -## Paths for Referring to Items +## Iteration Traits (`Iterator` and `IntoIterator`) -Paths are used to access items within the module tree, similar to navigating a filesystem. +The `Iterator` and `IntoIterator` traits facilitate iteration over collections in Cairo. -- **Absolute Path**: Starts from the crate root, beginning with the crate name. - - Example: `crate::front_of_house::hosting::add_to_waitlist();` -- **Relative Path**: Starts from the current module. - - Example: `front_of_house::hosting::add_to_waitlist();` -- **`super` Keyword**: Used to start a relative path from the parent module. - - Example: `super::deliver_order();` +- **`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. -## Privacy and the `pub` Keyword +This design guarantees type-safe iteration and improves code ergonomics. -Items are private by default. The `pub` keyword makes items accessible from outside their parent module. +```cairo, noplayground +// Collection type that contains a simple array +#[derive(Drop)] +pub struct ArrayIter { + array: Array, +} -- **Public Modules**: `pub mod module_name` makes the module accessible to ancestor modules. -- **Public Functions/Items**: `pub fn function_name()` makes the function accessible. -- **Public Structs**: `pub struct StructName` makes the struct public, but its fields remain private by default. Fields can be made public individually using `pub` before their declaration. -- **Public Enums**: `pub enum EnumName` makes the enum and all its variants public. - -**Example of making items public:** +// T is the collection type +pub trait Iterator { + type Item; + fn next(ref self: T) -> Option; +} -```cairo,noplayground -mod front_of_house { - pub mod hosting { - pub fn add_to_waitlist() {} +impl ArrayIterator of Iterator> { + type Item = T; + fn next(ref self: ArrayIter) -> Option { + self.array.pop_front() } } -pub fn eat_at_restaurant() { - // Absolute path - crate::front_of_house::hosting::add_to_waitlist(); // ✅ Compiles +/// Turns a collection of values into an iterator +pub trait IntoIterator { + /// The iterator type that will be created + type IntoIter; + impl Iterator: Iterator; - // Relative path - front_of_house::hosting::add_to_waitlist(); // ✅ Compiles + fn into_iter(self: T) -> Self::IntoIter; } -``` - -## Summary - -Cairo's module system organizes code and controls privacy. Items are private by default, and `pub` is used to expose modules, functions, structs, enums, and their fields. Paths (absolute, relative, and using `super`) are used to reference these items across the module tree. - -The `use` Keyword: Shortcuts, Aliasing, and Re-exporting - -# The `use` Keyword: Shortcuts, Aliasing, and Re-exporting - -Having to write out the full paths to call functions or refer to types can be repetitive. The `use` keyword allows you to create shortcuts for these paths, making your code more concise. - -## Bringing Paths into Scope with `use` - -The `use` keyword brings a path into the current scope, allowing you to use a shorter name. This is similar to creating a symbolic link. -```cairo -mod front_of_house { - pub mod hosting { - pub fn add_to_waitlist() {} +impl ArrayIntoIterator of IntoIterator> { + type IntoIter = ArrayIter; + fn into_iter(self: Array) -> ArrayIter { + ArrayIter { array: self } } } -use crate::front_of_house::hosting; - -pub fn eat_at_restaurant() { - hosting::add_to_waitlist(); // ✅ Shorter path -} ``` -Note that a `use` statement only applies to the scope in which it is declared. If you move the function to a different module, the shortcut will no longer be available in that new scope. - -## Creating Idiomatic `use` Paths +Advanced Language Features -It is idiomatic to bring a module into scope and then call its functions using the module name, rather than bringing the function itself directly into scope. +Ownership, References, and Snapshots -```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 +# Ownership, References, and Snapshots -pub fn eat_at_restaurant() { - add_to_waitlist(); -} -``` +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. -However, for structs, enums, and traits, it is idiomatic to bring the item itself into scope: +## References and Snapshots -```cairo -use core::num::traits::BitSize; +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. -#[executable] -fn main() { - let u8_size: usize = BitSize::::bits(); - println!("A u8 variable has {} bits", u8_size) -} -``` +### Snapshots -## Providing New Names with the `as` Keyword +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. -If you need to bring multiple items with the same name into scope, or if you simply want to rename an item, you can use the `as` keyword to create an alias. +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. ```cairo -use core::array::ArrayTrait as Arr; - #[executable] fn main() { - let mut arr = Arr::new(); // ArrayTrait was renamed to Arr - arr.append(1); -} -``` - -## Importing Multiple Items from the Same Module - -To import multiple items from the same module, you can use curly braces `{}` to list them. - -```cairo -mod shapes { - #[derive(Drop)] - pub struct Square { - pub side: u32, - } + let arr1: Array = array![]; - #[derive(Drop)] - pub struct Circle { - pub radius: u32, - } - - #[derive(Drop)] - pub struct Triangle { - pub base: u32, - pub height: u32, - } + let (arr2, len) = calculate_length(arr1); } -use shapes::{Circle, Square, Triangle}; +fn calculate_length(arr: Array) -> (Array, usize) { + let length = arr.len(); // len() returns the length of an array -#[executable] -fn main() { - let sq = Square { side: 5 }; - let cr = Circle { radius: 3 }; - let tr = Triangle { base: 5, height: 2 }; + (arr, length) } ``` -## Re-exporting Names in Module Files - -Re-exporting makes an item available in a new scope and also allows other code to bring that item into their scope using the `pub` keyword. - ```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(); -} +let second_length = calculate_length(@arr1); // Calculate the current length of the array ``` -This allows external code to access `add_to_waitlist` via `crate::hosting::add_to_waitlist()` instead of the longer original path. - -## Using External Packages in Cairo with Scarb - -Scarb allows you to use external packages by declaring them in the `[dependencies]` section of your `Scarb.toml` file, specifying the Git repository URL. - -```cairo -[dependencies] -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +```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. ``` -Organizing Modules into Separate Files - -# Organizing Modules into Separate Files - -When modules become large, you can move their definitions to separate files to improve code navigation. The Cairo compiler uses a convention to map module declarations to files. - -## Separating a Top-Level Module - -To extract a module (e.g., `front_of_house`) from the crate root file (`src/lib.cairo`): - -1. **Modify the crate root file:** Remove the module's body and leave only the `mod` declaration. - 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` +### Desnap Operator -2. **Create the module's file:** Place the removed module code into a new file named `src/front_of_house.cairo`. The compiler automatically associates this file with the `front_of_house` module declared in the crate root. - 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` - -## Separating a Nested Module - -To extract a child module (e.g., `hosting` within `front_of_house`) to its own file: - -1. **Modify the parent module file:** Change `src/front_of_house.cairo` to only declare the child module. - Filename: src/front_of_house.cairo - - ```cairo,noplayground - pub mod hosting; - ``` - -2. **Create the child module's file:** Create a new directory that mirrors the parent's path in the module tree (e.g., `src/front_of_house/`) and place the child module's file within it (e.g., `src/front_of_house/hosting.cairo`). - Filename: src/front_of_house/hosting.cairo - - ```cairo,noplayground - pub fn add_to_waitlist() {} - ``` - -The compiler's file-to-module mapping follows the module tree structure. Using `mod` is not an include operation; it declares a module's existence and location within the tree. Once declared, other files reference its items using paths. This approach allows for organizing code into separate files as modules grow, without altering the module tree's functionality. - -Generics in Cairo - -Introduction to Generics in Cairo - -# Introduction to Generics in Cairo - -Generics are a tool in Cairo that allow us to create abstract stand-ins for concrete types or other properties. This enables us to express behavior without knowing the exact types that will be used when the code is compiled and run. Functions can accept parameters of a generic type, similar to how they accept parameters with unknown values, allowing the same code to operate on multiple concrete types. An example of this is the `Option` enum encountered in Chapter 6. - -Generics help remove code duplication by enabling the replacement of specific types with a placeholder that represents multiple types. While the compiler generates specific definitions for each concrete type that replaces a generic type, thus reducing development time, it's important to note that code duplication still occurs at the compile level. This can lead to an increase in contract size, particularly when using generics for multiple types in Starknet contracts. - -Before delving into the syntax of generics, let's consider how to eliminate duplication by extracting a function. This involves replacing specific values with a placeholder that represents multiple values. By understanding how to identify and extract duplicated code into a function, we can better recognize situations where generics can be applied to further reduce duplication. - -Consider a program designed to find the largest number in an array of `u8`: +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. ```cairo -#[executable] -fn main() { - let mut number_list: Array = array![34, 50, 25, 100, 65]; - - let mut largest = number_list.pop_front().unwrap(); - - while let Some(number) = number_list.pop_front() { - if number > largest { - largest = number; - } - } - - println!("The largest number is {}", largest); +#[derive(Drop)] +struct Rectangle { + height: u64, + width: u64, } -``` - -This code initializes an array of `u8`, extracts the first element as the initial `largest` value, and then iterates through the remaining elements. If a number greater than the current `largest` is found, `largest` is updated. After processing all numbers, `largest` holds the maximum value. - -If we need to find the largest number in a second array, we could duplicate the existing code: -```cairo #[executable] fn main() { - let mut number_list: Array = array![34, 50, 25, 100, 65]; - - let mut largest = number_list.pop_front().unwrap(); - - while let Some(number) = number_list.pop_front() { - if number > largest { - largest = number; - } - } - - println!("The largest number is {}", largest); - - let mut number_list: Array = array![102, 34, 255, 89, 54, 2, 43, 8]; - - let mut largest = number_list.pop_front().unwrap(); - - while let Some(number) = number_list.pop_front() { - if number > largest { - largest = number; - } - } + let rec = Rectangle { height: 3, width: 10 }; + let area = calculate_area(@rec); + println!("Area: {}", area); +} - println!("The largest number is {}", largest); +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 } ``` -This duplication highlights the need for a more efficient approach, which generics provide. - -Defining Generic Functions, Structs, and Enums +Generics -# Defining Generic Functions, Structs, and Enums +# Generics -Generics in Cairo allow for the creation of reusable code that can operate on various concrete data types, thereby reducing duplication and enhancing maintainability. This applies to functions, structs, enums, traits, and implementations. +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. ## Generic Functions -Generic functions can operate on different types without requiring separate implementations for each. This is achieved by specifying type parameters in the function signature. +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. + +For example, a function to find the largest list can be implemented once using generics: ```cairo // Specify generic type T between the angulars @@ -4233,839 +3614,951 @@ fn main() { } ``` -To handle operations that require specific capabilities from generic types (like comparison or copying), trait bounds are used. For instance, `PartialOrd` enables comparison, `Copy` allows copying, and `Drop` manages resource cleanup. +## Generic Methods and Traits -```cairo -// 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]; +Cairo allows defining generic methods within generic traits. Consider a `Wallet` struct with generic types for balance and address: - // The index we will use to move through the list - let mut index = 1; +```cairo,noplayground +struct Wallet { + balance: T, + address: U, +} +``` - // Iterate through the whole list storing the smallest - while index < list.len() { - if *list[index] < smallest { - smallest = *list[index]; - } - index = index + 1; - } +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: - smallest +```cairo,noplayground +// This does not compile! +trait WalletMixTrait { + fn mixup(self: Wallet, other: Wallet) -> Wallet; } -#[executable] -fn main() { - let list: Array = array![5, 3, 10]; - - // We need to specify that we are passing a snapshot of `list` as an argument - let s = smallest_element(@list); - assert!(s == 3); +impl WalletMixImpl of WalletMixTrait { + fn mixup(self: Wallet, other: Wallet) -> Wallet { + Wallet { balance: self.balance, address: other.address } + } } ``` -When a generic type `T` requires `Copy` and `Drop` traits for operations within a generic function, these trait bounds must be explicitly included in the function signature. +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: ```cairo -fn smallest_element, impl TCopy: Copy, impl TDrop: Drop>( - list: @Array, -) -> T { - let mut smallest = *list[0]; - let mut index = 1; +trait WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet; +} - while index < list.len() { - if *list[index] < smallest { - smallest = *list[index]; - } - index = index + 1; +impl WalletMixImpl, U1, +Drop> of WalletMixTrait { + fn mixup, U2, +Drop>( + self: Wallet, other: Wallet, + ) -> Wallet { + Wallet { balance: self.balance, address: other.address } } - - smallest } ``` -### Anonymous Generic Implementation Parameter (`+` Operator) +Traits and Implementations -Trait implementations can be specified anonymously using the `+` operator for generic type parameters when the implementation itself is not directly used in the function body, only its constraint. - -```cairo -fn smallest_element, +Copy, +Drop>(list: @Array) -> T { -# let mut smallest = *list[0]; -# let mut index = 1; -# loop { -# if index >= list.len() { -# break smallest; -# } -# if *list[index] < smallest { -# smallest = *list[index]; -# } -# index = index + 1; -# } -# } -``` +# Traits and Implementations -## Structs +## `#[generate_trait]` Attribute -Structs can be defined with generic type parameters for their fields. +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. ```cairo -#[derive(Drop)] -struct Wallet { - balance: T, +#[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 w = Wallet { balance: 3 }; + let rect1 = Rectangle { width: 30, height: 50 }; + println!("Area is {}", rect1.area()); } ``` -This is equivalent to manually implementing the `Drop` trait for the struct, provided the generic type `T` also implements `Drop`. +## Snapshots and References -Structs can also accommodate multiple generic types. +Methods can accept `self` as a snapshot (`@`) if they don't modify the instance, or as a mutable reference (`ref self`) to allow modifications. ```cairo -#[derive(Drop)] -struct Wallet { - balance: T, - address: U, +#[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; + } } #[executable] fn main() { - let w = Wallet { balance: 3, address: 14 }; -} -``` + let mut rect2 = Rectangle { width: 10, height: 20 }; + rect2.scale(2); + println!("The new size is (width: {}, height: {})", rect2.width, rect2.height); +} +``` -## Enums +## Default Implementations -Enums can also be defined with generic type parameters for their variants. +Traits can provide default behavior for methods, allowing implementers to either use the default or override it. -```cairo,noplayground -enum Option { - Some: T, - None, +```cairo +// In src/lib.cairo +mod aggregator { + pub trait Summary { + fn summarize(self: @T) -> ByteArray { + "(Read more...)" + } + } + + #[derive(Drop)] + pub struct NewsArticle { + pub headline: ByteArray, + pub location: ByteArray, + pub author: ByteArray, + pub content: ByteArray, + } + + impl NewsArticleSummary of Summary {} + + #[derive(Drop)] + pub struct Tweet { + pub username: ByteArray, + pub content: ByteArray, + pub reply: bool, + pub retweet: bool, + } + + impl TweetSummary of Summary { + fn summarize(self: @Tweet) -> ByteArray { + format!("{}: {}", self.username, self.content) + } + } } -``` -Enums can also utilize multiple generic types, as seen in the `Result` enum. +use aggregator::{NewsArticle, Summary}; -```cairo,noplayground -enum Result { - Ok: T, - Err: E, +#[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", + }; + + println!("New article available! {}", news.summarize()); } ``` -Implementing Generic Methods and Traits +This code prints `New article available! (Read more...)`. -# Implementing Generic Methods and Traits +## Managing and Using External Traits -Methods can be implemented on generic structs and enums, utilizing their generic types. Traits can also be defined with generic types, requiring generic types in both trait and implementation definitions. +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. -## Generic Methods on Generic Structs +## Impl Aliases -A `Wallet` struct can have methods defined, such as `balance`, which returns the generic type `T`. This involves defining a trait, like `WalletTrait`, and then implementing it for the struct. +Implementations can be aliased upon import, which is useful for instantiating generic implementations with concrete types. ```cairo -#[derive(Copy, Drop)] -struct Wallet { - balance: T, +trait Two { + fn two() -> T; } -trait WalletTrait { - fn balance(self: @Wallet) -> T; +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() + } + } } -impl WalletImpl> of WalletTrait { - fn balance(self: @Wallet) -> T { - return *self.balance; +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. + +## Negative Impls + +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) + +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`. + +```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 } } +``` -#[executable] -fn main() { - let w = Wallet { balance: 50 }; - assert!(w.balance() == 50); +## Manual `Destruct` Implementation + +For generic structs containing types that require specific cleanup (like `Felt252Dict`), a manual implementation of the `Destruct` trait might be necessary if `#[derive(Destruct)]` is not sufficient or applicable. + +```cairo +impl UserDatabaseDestruct, +Felt252DictValue> of Destruct> { + fn destruct(self: UserDatabase) nopanic { + self.balances.squash(); + } } ``` -Constraints can be applied to generic types when defining methods. For example, methods can be implemented only for `Wallet`. +Advanced Type System Features + +# Negative Implementations + +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. + +To use this feature, you must enable it in your `Scarb.toml` file with `experimental-features = ["negative_impls"]` under the `[package]` section. + +Consider a scenario where you want all types to implement the `Consumer` trait by default, but restrict types that are already `Producer`s from being `Consumer`s. Negative implementations can enforce this. ```cairo -#[derive(Copy, Drop)] -struct Wallet { - balance: T, -} +#[derive(Drop)] +struct ProducerType {} + +#[derive(Drop, Debug)] +struct AnotherType {} + +#[derive(Drop, Debug)] +struct AThirdType {} -/// Generic trait for wallets -trait WalletTrait { - fn balance(self: @Wallet) -> T; +trait Producer { + fn produce(self: T) -> u32; } -impl WalletImpl> of WalletTrait { - fn balance(self: @Wallet) -> T { - return *self.balance; - } +trait Consumer { + fn consume(self: T, input: u32); } -/// Trait for wallets of type u128 -trait WalletReceiveTrait { - fn receive(ref self: Wallet, value: u128); +impl ProducerImpl of Producer { + fn produce(self: ProducerType) -> u32 { + 42 + } } -impl WalletReceiveImpl of WalletReceiveTrait { - fn receive(ref self: Wallet, value: u128) { - self.balance += value; +// 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); } } #[executable] fn main() { - let mut w = Wallet { balance: 50 }; - assert!(w.balance() == 50); + let producer = ProducerType {}; + let another_type = AnotherType {}; + let third_type = AThirdType {}; + let production = producer.produce(); - w.receive(100); - assert!(w.balance() == 150); + // producer.consume(production); // Invalid: ProducerType does not implement Consumer + another_type.consume(production); + third_type.consume(production); } ``` -## Generic Methods in Generic Traits +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. -Generic methods can be defined within generic traits. When combining generic types from multiple structs, ensuring all generic types implement `Drop` is crucial for compilation, especially if instances are dropped within the method. +Function Safety and Panics -The following demonstrates a trait `WalletMixTrait` with a `mixup` method that combines two wallets of potentially different generic types into a new wallet. The implementation requires `Drop` constraints on the generic types involved. +## Function Safety and Panics -```cairo -trait WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet; -} +## `nopanic` Notation -impl WalletMixImpl, U1, +Drop> of WalletMixTrait { - fn mixup, U2, +Drop>( - self: Wallet, other: Wallet, - ) -> Wallet { - Wallet { balance: self.balance, address: other.address } - } +The `nopanic` notation indicates that a function will never panic. Only `nopanic` functions can be called within another function annotated as `nopanic`. + +A function guaranteed to never panic: + +```cairo,noplayground +fn function_never_panic() -> felt252 nopanic { + 42 } ``` -## Associated Types vs. Separate Generic Parameters - -When defining generic functions that operate on types with specific return types (e.g., packing two `u32` into a `u64`), associated types in traits can offer a cleaner syntax compared to defining separate generic parameters for the return type. +This function will always return `42` and is guaranteed not to panic. -A function `foo` using `PackGeneric`: +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: -```cairo -fn foo>(self: T, other: T) -> U { - self.pack_generic(other) +```cairo,noplayground +fn function_never_panic() nopanic { + assert!(1 == 1, "what"); } ``` -Compared to a function `bar` using an associated type `Result` in the `Pack` trait: +Compiling such a function yields an error indicating that a `nopanic` function calls another function that may panic. + +## `panic_with` Attribute + +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`. + +Example: ```cairo -fn bar>(self: T, b: T) -> PackImpl::Result { - PackImpl::pack(self, b) +#[panic_with('value is 0', wrap_not_zero)] +fn wrap_if_not_zero(value: u128) -> Option { + if value == 0 { + None + } else { + Some(value) + } } -``` -Traits in Cairo - -Introduction to Traits +#[executable] +fn main() { + wrap_if_not_zero(0); // this returns None + wrap_not_zero(0); // this panics with 'value is 0' +} +``` -# Introduction to Traits +Storage Optimization and Modularity -A trait defines a set of methods that can be implemented by a type. These methods can be called on instances of the type when this trait is implemented. Traits, when combined with generic types, define functionality that a particular type has and can share with other types, allowing for the definition of shared behavior in an abstract way. +# Storage Optimization and Modularity -Trait bounds can be used to specify that a generic type must possess certain behaviors. Traits are similar to interfaces found in other programming languages, though some differences exist. While traits can be defined without generic types, they are most powerful when used in conjunction with them. +## Storage Packing -## Defining a Trait +The `StorePacking` trait allows for optimizing storage by packing multiple fields into a single storage variable. This is achieved using bitwise operations: -A type's behavior is determined by the methods callable on it. Different types share common behavior if the same methods can be invoked on all of them. Trait definitions serve to group method signatures, thereby defining a set of behaviors essential for a specific purpose. +- **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. -Defining and Implementing Traits +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. -# Defining and Implementing Traits +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`. -Traits define shared behavior that can be implemented across different types. +```rust +// Example demonstrating storage packing (conceptual, actual implementation details may vary) +const TWO_POW_8: u128 = 1 << 8; +const TWO_POW_40: u128 = 1 << 40; +const MASK_8: u128 = (1 << 8) - 1; +const MASK_32: u128 = (1 << 32) - 1; -## Defining a Trait +#[derive(Copy, Drop, Serde)] +struct MyStruct { + field1: u8, + field2: u32, + field3: u8, +} -A trait is declared using the `trait` keyword, followed by its name. Inside the trait definition, you declare method signatures. These signatures specify the behavior without providing an implementation, ending with a semicolon. Traits can be made public using `pub` so they can be used by other crates. +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 + } -```cairo,noplayground -# #[derive(Drop, Clone)] -# struct NewsArticle { -# headline: ByteArray, -# location: ByteArray, -# author: ByteArray, -# content: ByteArray, -# } -# -pub trait Summary { - fn summarize(self: @NewsArticle) -> ByteArray; + 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 } + } } ``` -The `ByteArray` type is used for strings in Cairo. - -## Implementing a Trait +## Components -To implement a trait for a type, use the `impl` keyword, followed by an implementation name, the `of` keyword, and the trait name. If the trait is generic, specify the generic type in angle brackets. Inside the implementation block, provide the method bodies for the trait's methods. +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. -```cairo,noplayground -# mod aggregator { -# pub trait Summary { -# fn summarize(self: @T) -> ByteArray; -# } -# - #[derive(Drop)] - pub struct NewsArticle { - pub headline: ByteArray, - pub location: ByteArray, - pub author: ByteArray, - pub content: ByteArray, - } +Functional Programming: Closures and Iterators - impl NewsArticleSummary of Summary { - fn summarize(self: @NewsArticle) -> ByteArray { - format!("{} by {} ({})", self.headline, self.author, self.location) - } - } +# Functional Programming: Closures and Iterators - #[derive(Drop)] - pub struct Tweet { - pub username: ByteArray, - pub content: ByteArray, - pub reply: bool, - pub retweet: bool, - } +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. - impl TweetSummary of Summary { - fn summarize(self: @Tweet) -> ByteArray { - format!("{}: {}", self.username, self.content) - } - } -# } -# -# use aggregator::{NewsArticle, Summary, Tweet}; -# -# #[executable] -# fn main() { -# let news = NewsArticle { -# headline: "Cairo has become the most popular language for developers", -# location: "Worldwide", -# author: "Cairo Digger", -# content: "Cairo is a new programming language for zero-knowledge proofs", -# }; -# -# 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 -# -# println!("New article available! {}", news.summarize()); -# println!("New tweet! {}", tweet.summarize()); -# } -# -# -``` +> Note: Closures were introduced in Cairo 2.9 and are still under development. -Users of a crate must bring the trait into scope to use its methods on their types. +## Understanding Closures -## Generic Traits +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. -Traits can be generic over types. This allows a single trait definition to describe behavior for any type that implements it. +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. -```cairo,noplayground -# mod aggregator { - pub trait Summary { - fn summarize(self: @T) -> ByteArray; - } -# -# #[derive(Drop)] -# pub struct NewsArticle { -# pub headline: ByteArray, -# pub location: ByteArray, -# pub author: ByteArray, -# pub content: ByteArray, -# } -# -# impl NewsArticleSummary of Summary { -# fn summarize(self: @NewsArticle) -> ByteArray { -# format!("{} by {} ({})", self.headline, self.author, self.location) -# } -# } -# -# #[derive(Drop)] -# pub struct Tweet { -# pub username: ByteArray, -# pub content: ByteArray, -# pub reply: bool, -# pub retweet: bool, -# } -# -# impl TweetSummary of Summary { -# fn summarize(self: @Tweet) -> ByteArray { -# format!("{}: {}", self.username, self.content) -# } -# } -# } -# -# use aggregator::{NewsArticle, Summary, Tweet}; -# -# #[executable] -# fn main() { -# let news = NewsArticle { -# headline: "Cairo has become the most popular language for developers", -# location: "Worldwide", -# author: "Cairo Digger", -# content: "Cairo is a new programming language for zero-knowledge proofs", -# }; -# -# 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 -# -# println!("New article available! {}", news.summarize()); -# println!("New tweet! {}", tweet.summarize()); -# } -# -# -``` +## Implementing Your Functional Programming Patterns with Closures -## Default Implementations +Closures can be passed as function arguments, a mechanism heavily utilized in functional programming through functions like `map`, `filter`, and `reduce`. -Traits can provide default behavior for their methods. Types implementing the trait can then choose to override these defaults or use them as is. +Here's a potential implementation of `map` to apply a function to all items in an array: -```cairo -# mod aggregator { - pub trait Summary { - fn summarize(self: @T) -> ByteArray { - "(Read more...)".into() - } - } -# -# #[derive(Drop)] -# pub struct NewsArticle { -# pub headline: ByteArray, -# pub location: ByteArray, -# pub author: ByteArray, -# pub content: ByteArray, -# } -# -# impl NewsArticleSummary of Summary {} -# -# #[derive(Drop)] -# pub struct Tweet { -# pub username: ByteArray, -# pub content: ByteArray, -# pub reply: bool, -# pub retweet: bool, -# } -# -# impl TweetSummary of Summary { -# fn summarize(self: @Tweet) -> ByteArray { -# format!("(Read more from {}...)", Self::summarize_author(self)) -# } -# fn summarize_author(self: @Tweet) -> ByteArray { -# format!("@{}", self.username) -# } -# } -# } -# -# use aggregator::{NewsArticle, Summary}; -# -# #[executable] -# fn main() { -# let news = NewsArticle { -# headline: "Cairo has become the most popular language for developers", -# location: "Worldwide", -# author: "Cairo Digger", -# content: "Cairo is a new programming language for zero-knowledge proofs", -# }; -# -# println!("New article available! {}", news.summarize()); -# } -# -# -``` - -A default implementation can also call other methods defined within the trait, provided those methods are also implemented by the type. - -```cairo -# mod aggregator { -# pub trait Summary { -# fn summarize( -# self: @T, -# ) -> ByteArray { -# format!("(Read more from {}...)", Self::summarize_author(self)) -# } -# fn summarize_author(self: @T) -> ByteArray; -# } -# -# #[derive(Drop)] -# pub struct Tweet { -# pub username: ByteArray, -# pub content: ByteArray, -# pub reply: bool, -# pub retweet: bool, -# } -# - impl TweetSummary of Summary { - fn summarize_author(self: @Tweet) -> ByteArray { - format!("@{}", self.username) +```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 } -# } -# -# use aggregator::{Summary, Tweet}; -# -# #[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()); -# } -# -# -``` - -## The `PartialEq` Trait - -The `PartialEq` trait allows types to be compared for equality. It can be derived for structs and enums. - -When `PartialEq` is derived: - -- For structs, two instances are equal only if all their fields are equal. -- For enums, each variant is equal to itself and not equal to other variants. - -You can also implement `PartialEq` manually for custom equality logic. For example, two rectangles can be considered equal if they have the same area. - -```cairo -#[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) +#[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 } } #[executable] fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - let rect2 = Rectangle { width: 50, height: 30 }; + let double = |value| value * 2; + println!("Double of 2 is {}", double(2_u8)); + println!("Double of 4 is {}", double(4_u8)); - println!("Are rect1 and rect2 equal? {}", rect1 == rect2); -} -``` + // This won't work because `value` type has been inferred as `u8`. + //println!("Double of 6 is {}", double(6_u16)); -The `PartialEq` trait is necessary for using the `assert_eq!` macro in tests. + let sum = |x: u32, y: u32, z: u16| { + x + y + z.into() + }; + println!("Result: {}", sum(1, 2, 3)); -```cairo -#[derive(PartialEq, Drop)] -struct A { - item: felt252, -} + let x = 8; + let my_closure = |value| { + x * (value + 3) + }; -#[executable] -fn main() { - let first_struct = A { item: 2 }; - let second_struct = A { item: 2 }; - assert!(first_struct == second_struct, "Structs are different"); + println!("my_closure(1) = {}", my_closure(1)); + + 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 + }); + + println!("double: {:?}", double); + println!("another: {:?}", another); + + let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); + println!("even: {:?}", even); } ``` -## Serialization with `Serde` +Resource Management and Memory Safety -`Serde` provides trait implementations for `serialize` and `deserialize` functions, enabling the transformation of data structures into arrays and vice-versa. +# Resource Management and Memory Safety -Using Traits and External Implementations +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. -# Using Traits and External Implementations +## Smart Pointers -To use trait methods, you must import the correct traits and their implementations. If trait implementations are in separate modules, you might need to import both the trait and its implementation. +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. -## Default Implementations +Operator Overloading and Hashing -Traits can provide default implementations for their methods. If a type implements a trait without overriding a method, the default implementation is used. +# Operator Overloading and Hashing -For example, to use a default implementation of the `summarize` method for `NewsArticle`, you can specify an empty `impl` block: +## Operator Overloading -```cairo -// Assuming Summary trait and NewsArticle struct are defined elsewhere -impl NewsArticleSummary of Summary {} -``` +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. -This allows calling the `summarize` method on `NewsArticle` instances, which will use the default behavior: +For example, combining two `Potion` structs, which have `health` and `mana` fields, can be done using the `+` operator by implementing the `Add` trait: ```cairo -use aggregator::{NewsArticle, Summary}; +struct Potion { + health: felt252, + mana: felt252, +} + +impl PotionAdd of Add { + fn add(lhs: Potion, rhs: Potion) -> Potion { + Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } + } +} #[executable] fn main() { - let 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", - }; - - println!("New article available! {}", news.summarize()); + 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); } ``` -This code prints `New article available! (Read more...)`. - -## Managing and Using External Trait +## Hashing -When trait implementations are defined in different modules than the trait itself, explicit imports are necessary. +### When to Use Them? -Consider `Listing 8-6`, where `ShapeGeometry` is implemented for `Rectangle` and `Circle` in their respective modules: +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. -```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; -} +### Working with Hashes -mod rectangle { - // Importing ShapeGeometry is required to implement this trait for Rectangle - use super::ShapeGeometry; +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`. - #[derive(Copy, Drop)] - pub struct Rectangle { - pub height: u64, - pub width: u64, - } +The `HashStateTrait` and `HashStateExTrait` define basic methods for managing hash states: initializing, updating with values, and finalizing the computation. - // Implementation RectangleGeometry passes in - // to implement the trait for that type - impl RectangleGeometry of ShapeGeometry { - fn boundary(self: Rectangle) -> u64 { - 2 * (self.height + self.width) - } - fn area(self: Rectangle) -> u64 { - self.height * self.width - } - } +```cairo,noplayground +/// A trait for hash state accumulators. +trait HashStateTrait { + fn update(self: S, value: felt252) -> S; + fn finalize(self: S) -> felt252; } -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 - } - } +/// Extension trait for hash state accumulators. +trait HashStateExTrait { + /// Updates the hash state with the given value. + fn update_with(self: S, value: T) -> S; } -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 +/// 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; } ``` -In this example, `CircleGeometry` and `RectangleGeometry` do not need to be public. The compiler finds the appropriate implementation for the public `ShapeGeometry` trait. +### Advanced Hashing: Hashing Arrays with Poseidon -## Impl Aliases +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. -Implementations can be aliased when imported, which is useful for instantiating generic implementations with concrete types. This allows exposing specific implementations publicly while keeping the generic implementation private. +First, import the necessary traits and function: ```cairo,noplayground -trait Two { - fn two() -> T; -} +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::{PoseidonTrait, poseidon_hash_span}; +``` -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() - } - } -} +Define the struct. Deriving `Hash` on this struct would fail due to the `Array` field. -pub impl U8Two = one_based::TwoImpl; -pub impl U128Two = one_based::TwoImpl; +```cairo, noplayground +#[derive(Drop)] +struct StructForHashArray { + first: felt252, + second: felt252, + third: Array, +} ``` -This approach, shown in `Listing 8-7`, avoids code duplication and maintains a clean public API. - -## Contract Interfaces and Implementations +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. -Traits ensure that contract implementations adhere to their declared interfaces. A compilation error occurs if an implementation's function signature does not match the trait's. For instance, an incorrect `set` function signature in an `ISimpleStorage` implementation would fail to compile. +```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] }; -```cairo,noplayground - #[abi(embed_v0)] - impl SimpleStorage of super::ISimpleStorage { - // Incorrect signature: expected 2 parameters, got 1 - fn set(ref self: ContractState) {} - fn get(self: @ContractState) -> u128 { - self.stored_data.read() - } - } + 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(); +} ``` -The compiler would report an error like: "The number of parameters in the impl function `SimpleStorage::set` is incompatible with `ISimpleStorage::set`. Expected: 2, actual: 1." +Function Inlining and Output -Core Cairo Traits +# Function Inlining and Output -# Core Cairo Traits +## Function Inlining -## `Debug` for Debugging +Function inlining is an optimization technique where the body of a function is directly inserted into the code at the call site, rather than making a traditional function call. This can improve performance by eliminating the overhead associated with function calls, such as stack manipulation and jumps. -The `Debug` trait allows instances of a type to be printed for debugging purposes. It can be derived using `#[derive(Debug)]` and is required by `assert_xx!` macros in tests. +### How Inlining Works + +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. + +**Example:** + +Consider a program with an inlined function and a non-inlined function: ```cairo -#[derive(Copy, Drop, Debug)] -struct Point { - x: u8, - y: u8, +#[executable] +fn main() { + inlined(); + not_inlined(); +} + +#[inline(always)] +fn inlined() -> felt252 { + 'inlined' +} + +#[inline(never)] +fn not_inlined() -> felt252 { + 'not inlined' } +``` + +The corresponding Sierra code for this example shows a `function_call` for `not_inlined` but not for `inlined`. + +### Benefits and Drawbacks + +- **Benefits:** + + - Reduces function call overhead. + - Can enable further compiler optimizations by exposing the inlined code to the surrounding context. + +- **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. + +## Printing + +Cairo provides macros for printing data to the console, useful for program execution and debugging. + +### Standard Data Types + +Two macros are available for printing standard data types: +- `println!`: Prints output followed by a newline. +- `print!`: Prints output on the same line. + +Both macros accept a `ByteArray` string as the first argument. This string can be a simple message or include placeholders for formatting values. + +#### Placeholders + +Placeholders within the string can be used in two ways: + +- 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. + +These placeholder methods can be mixed. + +**Example:** + +```cairo #[executable] fn main() { - let p = Point { x: 1, y: 3 }; - println!("{:?}", p); + let a = 10; + let b = 20; + let c = 30; + + println!("Hello world!"); + println!("{} {} {}", a, b, c); // Output: 10 20 30 + println!("{c} {a} {}", b); // Output: 30 10 20 } ``` -## `Default` for Default Values +Deref Coercion + +# Deref Coercion + +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. + +## Understanding Deref Coercion with an Example -The `Default` trait enables the creation of a default value for a type, typically zero. Primitive types implement `Default` by default. For composite types, all elements must implement `Default`. Enums require a `#[default]` attribute on one variant. +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`. ```cairo -#[derive(Default, Drop)] -struct A { - item1: felt252, - item2: u64, +#[derive(Drop, Copy)] +struct UserProfile { + username: felt252, + email: felt252, + age: u16, } -#[derive(Default, Drop, PartialEq)] -enum CaseWithDefault { - A: felt252, - B: u128, - #[default] - C: u64, +#[derive(Drop, Copy)] +struct Wrapper { + value: T, +} + +impl DerefWrapper of Deref> { + type Target = T; + fn deref(self: Wrapper) -> T { + self.value + } } +``` + +This implementation of `Deref` for `Wrapper` simply returns the wrapped `value`. + +## Practical Application of Deref Coercion +When you have an instance of `Wrapper`, deref coercion allows you to access fields like `username` and `age` directly, as if you were operating on a `UserProfile` instance. + +```cairo #[executable] fn main() { - let defaulted: A = Default::default(); - assert!(defaulted.item1 == 0_felt252, "item1 mismatch"); - assert!(defaulted.item2 == 0_u64, "item2 mismatch"); + let wrapped_profile = Wrapper { + value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, + }; + // Access fields directly via deref coercion + println!("Username: {}", wrapped_profile.username); + println!("Current age: {}", wrapped_profile.age); +} +``` - let default_case: CaseWithDefault = Default::default(); - assert!(default_case == CaseWithDefault::C(0_u64), "case mismatch"); +## Restricting Deref Coercion to Mutable Variables + +The `DerefMut` trait, similar to `Deref`, enables coercion but is specifically applicable to mutable variables. It's important to note that `DerefMut` does not inherently provide mutable access to the underlying data; it's about the mutability context of the variable itself. + +```cairo, noplayground +//TAG: does_not_compile + +use core::ops::DerefMut; + +#[derive(Drop, Copy)] +struct UserProfile { + username: felt252, + email: felt252, + age: u16, +} + +#[derive(Drop, Copy)] +struct Wrapper { + value: T, +} + +impl DerefMutWrapper> of DerefMut> { + type Target = T; + fn deref_mut(ref self: Wrapper) -> T { + self.value + } +} + +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); +} + +#[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); } ``` -## `PartialEq` for Equality Comparisons +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. -The `PartialEq` trait enables equality comparisons between instances of a type using the `==` and `!=` operators. +```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); + ^^^^^^^^ -## `Copy` Trait +error: could not compile `no_listing_09_deref_coercion_example` due to previous error +``` -The `Copy` trait allows types to be duplicated by copying felts, bypassing Cairo's default move semantics. It's implemented for types where duplication is safe and efficient. Basic types implement `Copy` by default. Custom types can derive `Copy` if all their components also implement `Copy`. +To resolve this, the variable `wrapped_profile` must be declared as mutable. -```cairo,ignore_format -#[derive(Copy, Drop)] -struct Point { - x: u128, - y: u128, +```cairo, noplayground +//TAG: does_not_compile + +use core::ops::DerefMut; + +#[derive(Drop, Copy)] +struct UserProfile { + username: felt252, + email: felt252, + age: u16, +} + +#[derive(Drop, Copy)] +struct Wrapper { + value: T, +} + +impl DerefMutWrapper> of DerefMut> { + type Target = T; + fn deref_mut(ref self: Wrapper) -> T { + self.value + } +} + +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); } #[executable] fn main() { - let p1 = Point { x: 5, y: 10 }; - foo(p1); - foo(p1); + 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); } +``` -fn foo(p: Point) { // do something with p +## Calling Methods via Deref Coercion + +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`. + +```cairo +struct MySource { + pub data: u8, +} + +struct MyTarget { + pub data: u8, +} + +#[generate_trait] +impl TargetImpl of TargetTrait { + fn foo(self: MyTarget) -> u8 { + self.data + } +} + +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); } ``` -Trait Bounds and Generics +In this example, `MySource` dereferences to `MyTarget`, which has a method `foo`. This allows `foo` to be called directly on `source`. + +## Summary + +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. + +Associated Types and Data Packing -# Trait Bounds and Generics +# Associated Types and Data Packing -Trait bounds allow you to specify which traits a generic type must implement, ensuring that the generic type can be used safely within the function's logic. +## Constraint Traits on Associated Items -## Ensuring Droppability with Trait Bounds +Associated items are an experimental feature. To use them, add `experimental-features = ["associated_item_constraints"]` to your `Scarb.toml`. -When a function operates on generic types, the compiler needs to guarantee that certain operations are possible. For instance, if a function needs to drop a generic array `Array`, it must ensure that `T` itself is droppable. This is achieved by adding a trait bound to the generic type. +You can constrain associated items of a trait based on a generic parameter's type using the `[AssociatedItem: ConstrainedValue]` syntax after a trait bound. -Consider the `largest_list` function, which returns the longer of two arrays. Initially, it might fail if `T` is not guaranteed to be droppable. The corrected function signature includes a trait bound for `Drop`: +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. ```cairo -fn largest_list>(l1: Array, l2: Array) -> Array { - if l1.len() > l2.len() { - l1 - } else { - l2 +trait Extend { + fn extend[Item: A], +Destruct>(ref self: T, iterator: I); +} + +impl ArrayExtend> of Extend, T> { + fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { + for item in iterator { + self.append(item); + } } } ``` -This signature ensures that any type `T` used with `largest_list` must implement the `Drop` trait. +## `TypeEqual` Trait for Type Equality Constraints + +The `TypeEqual` trait from `core::metaprogramming` allows constraints based on type equality. + +### Excluding Specific Types + +`TypeEqual` can be used with negative implementations to exclude specific types from a trait implementation. -## Constraints for Generic Types +```cairo +trait SafeDefault { + fn safe_default() -> T; +} + +#[derive(Drop, Default)] +struct SensitiveData { + secret: felt252, +} -Adding trait bounds not only satisfies compiler requirements but also enables more effective function logic. For example, to find the smallest element in a list of a generic type `T`, `T` must implement the `PartialOrd` trait to allow comparisons. +// Implement SafeDefault for all types EXCEPT SensitiveData +impl SafeDefaultImpl< + T, +Default, -core::metaprogramming::TypeEqual, +> of SafeDefault { + fn safe_default() -> T { + Default::default() + } +} -## Using `TypeEqual` for Associated Types +#[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(); +} +``` -Trait bounds can also enforce constraints on associated types of generic types. The `TypeEqual` trait from `core::metaprogramming` can be used to ensure that associated types of different generic types are the same. +### Ensuring Matching Associated Types -In the following example, the `combine` function requires that the `State` associated types of `StateMachine` implementations `A` and `B` are equal: +`TypeEqual` is useful for ensuring two types have equal associated types, especially in generic functions. ```cairo trait StateMachine { @@ -5110,24 +4603,45 @@ fn main() { } ``` -This example demonstrates how `TypeEqual` ensures that both `TA` and `TB` use the same `State` type (`StateCounter`) before their `transition` methods are called within `combine`. +## Data Packing with Associated Types -Associated Items +Associated types can simplify function signatures compared to explicitly defining generic type parameters for return types. -# Associated Items +Consider a `PackGeneric` trait that requires explicit generic parameters for the input and output types: -Associated items are definitions that are logically related to an implementation. Every associated item kind comes in two varieties: definitions that contain the actual implementation and declarations that declare signatures for definitions. - -## Associated Types - -Associated types are type aliases within traits that allow trait implementers to choose the actual types to use. This keeps trait definitions clean and flexible, as the concrete type is chosen by the implementer and doesn't need to be specified when using the trait. +```cairo +fn foo>(self: T, other: T) -> U { + self.pack_generic(other) +} +``` -Consider the `Pack` trait: +A `Pack` trait using an associated type `Result` allows for a more concise function signature: -```cairo, noplayground +```cairo trait Pack { type Result; + fn pack(self: T, other: T) -> Self::Result; +} + +impl PackU32Impl of Pack { + type Result = u64; + + 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) +} +``` + +Both approaches achieve the same result: +```cairo +trait Pack { + type Result; fn pack(self: T, other: T) -> Self::Result; } @@ -5171,1070 +4685,1196 @@ fn main() { println!("x: {}", x); println!("y: {}", y); } +``` +Modules and Project Organization -``` +Project Setup with Scarb -Using associated types, `bar` is defined more concisely than a generic approach like `foo`: +# Project Setup with Scarb -```cairo, noplayground -# trait Pack { -# type Result; -# -# fn pack(self: T, other: T) -> Self::Result; -# } -# -# impl PackU32Impl of Pack { -# type Result = u64; -# -# 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) -} -# -# trait PackGeneric { -# fn pack_generic(self: T, other: T) -> U; -# } -# -# impl PackGenericU32 of PackGeneric { -# fn pack_generic(self: u32, other: u32) -> u64 { -# let shift: u64 = 0x100000000; // 2^32 -# self.into() * shift + other.into() -# } -# } -# -# 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); -# } -# -# -``` +## Scarb.toml Configuration -## Associated Constants +The `Scarb.toml` file configures your Scarb project. Key sections include: -Associated constants are constants associated with a type, declared using the `const` keyword in a trait and defined in its implementation. +- **`[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`. -```cairo, noplayground -trait Shape { - const SIDES: u32; - fn describe() -> ByteArray; -} +### Example Scarb.toml for a Cairo Program -struct Triangle {} +```toml +[package] +name = "hello_world" +version = "0.1.0" +edition = "2024_07" -impl TriangleShape of Shape { - const SIDES: u32 = 3; - fn describe() -> ByteArray { - "I am a triangle." - } -} +[cairo] +enable-gas = false -struct Square {} +[dependencies] +cairo_execute = "2.12.0" -impl SquareShape of Shape { - const SIDES: u32 = 4; - fn describe() -> ByteArray { - "I am a square." - } -} +[[target.executable]] +name = "hello_world_main" +function = "hello_world::hello_world::main" +``` -fn print_shape_info>() { - println!("I have {} sides. {}", ShapeImpl::SIDES, ShapeImpl::describe()); -} +## Managing Dependencies -#[executable] -fn main() { - print_shape_info::(); - print_shape_info::(); -} +Scarb manages external packages (crates) through Git repositories. -``` +### Adding Dependencies -Benefits of associated constants include keeping constants tied to traits, enabling compile-time checks, and ensuring consistency. +- **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 `. -## Associated Implementations +### Removing Dependencies -Associated implementations allow declaring that a trait implementation must exist for an associated type. This enforces relationships between types and implementations at the trait level, ensuring type safety and consistency, particularly in generic programming. +- Remove the corresponding line from `Scarb.toml` or use the `scarb rm ` command. -Additionally, associated items can be constrained based on generic parameters using the `[AssociatedItem: ConstrainedValue]` syntax. For example, to ensure an iterator's elements match a collection's type: +### Building with Dependencies -```cairo -trait Extend { - fn extend[Item: A], +Destruct>(ref self: T, iterator: I); -} +Run `scarb build` to fetch and compile all declared dependencies. -impl ArrayExtend> of Extend, T> { - fn extend[Item: T], +Destruct>(ref self: Array, iterator: I) { - for item in iterator { - self.append(item); - } - } -} -``` +## Using the Glob Operator (`*`) -Advanced Trait Features +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. -# Advanced Trait Features +```rust +use core::num::traits::*; +``` -## Default Implementations +Core Concepts: Packages, Crates, and Modules -Default implementations allow traits to provide functionality that implementors can optionally override. This enables traits to offer a base level of functionality, requiring implementors to only define specific methods. +# Core Concepts: Packages, Crates, and Modules -Traits can call other methods within the same trait, even those without default implementations. This allows for a modular design where a trait provides extensive functionality based on a minimal set of required methods. +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. -```cairo -# mod aggregator { - pub trait Summary { - fn summarize( - self: @T, - ) -> ByteArray { - format!("(Read more from {}...)", Self::summarize_author(self)) - } - fn summarize_author(self: @T) -> ByteArray; - } -# -# #[derive(Drop)] -# pub struct Tweet { -# pub username: ByteArray, -# pub content: ByteArray, -# pub reply: bool, -# pub retweet: bool, -# } -# -# impl TweetSummary of Summary { -# fn summarize_author(self: @Tweet) -> ByteArray { -# format!("@{}", self.username) -# } -# } -# } -# -# use aggregator::{Summary, Tweet}; -# -# #[executable] -# fn main() { -# let tweet = Tweet { -# username: "EliBenSasson", -# content: "Crypto is full of short-term maximizing projects. - @Starknet and @StarkWareLtd are about long-term vision maximization.", -# reply: false, -# retweet: false, -# }; -# -# println!("1 new tweet: {}", tweet.summarize()); -# } -# -# -``` +## Key Components of the Module System -To use this version of `Summary`, only `summarize_author` needs to be defined when implementing the trait for a type. +- **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. -## Negative Implementations +## Packages and Crates -Negative implementations (or negative traits/bounds) allow expressing that a type does _not_ implement a trait when defining an implementation for a generic type. This enables conditional implementations based on the absence of another implementation in the current scope. +### What is a Crate? -For example, to prevent a type from being both a `Producer` and a `Consumer`, negative implementations can be used. A `ProducerType` can implement `Producer`, while other types that do not implement `Producer` can be granted a default `Consumer` implementation. +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. -```cairo -#[derive(Drop)] -struct ProducerType {} +### What is the Crate Root? -#[derive(Drop, Debug)] -struct AnotherType {} +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. -#[derive(Drop, Debug)] -struct AThirdType {} +### What is a Package? -trait Producer { - fn produce(self: T) -> u32; -} +A Cairo package is a directory containing: -trait Consumer { - fn consume(self: T, input: u32); -} +- A `Scarb.toml` manifest file with a `[package]` section. +- Associated source code. -impl ProducerImpl of Producer { - fn produce(self: ProducerType) -> u32 { - 42 - } -} +A package can contain other packages, each with its own `Scarb.toml`. -impl TConsumerImpl, +Drop, -Producer> of Consumer { - fn consume(self: T, input: u32) { - println!("{:?} consumed value: {}", self, input); - } -} +### Creating a Package with Scarb -#[executable] -fn main() { - let producer = ProducerType {}; - let another_type = AnotherType {}; - let third_type = AThirdType {}; - let production = producer.produce(); +The `scarb new` command creates a new Cairo package: - // producer.consume(production); Invalid: ProducerType does not implement Consumer - another_type.consume(production); - third_type.consume(production); -} +```bash +scarb new my_package ``` -**Note:** This feature requires enabling `experimental-features = ["negative_impls"]` in `Scarb.toml`. - -## `TypeEqual` Trait +This generates a directory structure like: -The `TypeEqual` trait from `core::metaprogramming` facilitates constraints based on type equality. While often achievable with generic arguments and associated type constraints, `TypeEqual` is useful in advanced scenarios. +``` +my_package/ +├── Scarb.toml +└── src/ + └── lib.cairo +``` -### Excluding Specific Types from Implementations +- `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. -`TypeEqual` can be used with negative implementations to exclude specific types from a trait implementation. For instance, a `SafeDefault` trait can be implemented for all types with a `Default` trait, except for a `SensitiveData` type. +Example `Scarb.toml`: -```cairo -trait SafeDefault { - fn safe_default() -> T; -} +```toml +[package] +name = "my_package" +version = "0.1.0" +edition = "2024_07" -#[derive(Drop, Default)] -struct SensitiveData { - secret: felt252, -} +[executable] -// Implement SafeDefault for all types EXCEPT SensitiveData -impl SafeDefaultImpl< - T, +Default, -core::metaprogramming::TypeEqual, -> of SafeDefault { - fn safe_default() -> T { - Default::default() - } -} +[cairo] +enable-gas = false -#[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(); -} +[dependencies] +cairo_execute = "2.12.0" ``` -### Ensuring Type Equality +Additional `.cairo` files can be added to `src/` or its subdirectories to organize code into multiple files. -`TypeEqual` is also useful for ensuring that two types are equal, particularly when dealing with associated types. +Module Paths and Navigation -Associated Implementations and Iteration +# Module Paths and Navigation -# Associated Implementations and Iteration +When organizing code, Cairo follows specific rules for module declaration and navigation. -The `Iterator` and `IntoIterator` traits from the Cairo core library demonstrate the utility of associated implementations. +## Declaring Modules and Submodules -### `Iterator` and `IntoIterator` Traits +- **Crate Root**: The compiler starts by looking in the crate root file (`src/lib.cairo`). +- **Declaring Modules**: In the crate root, modules are declared using `mod ;`. The compiler searches for the module's code in: + - Inline, within curly brackets: + ```cairo,noplayground + // crate root file (src/lib.cairo) + mod garden { + // code defining the garden module goes here + } + ``` + - In the file `src/.cairo`. +- **Declaring Submodules**: In files other than the crate root (e.g., `src/garden.cairo`), submodules are declared similarly (e.g., `mod vegetables;`). The compiler searches within the parent module's directory: + - Inline, within curly brackets: + ```cairo,noplayground + // src/garden.cairo file + mod vegetables { + // code defining the vegetables submodule goes here + } + ``` + - In the file `src//.cairo`. -- **`IntoIterator` Trait**: This trait is responsible for converting a collection into an iterator. -- **`IntoIter` Associated Type**: This associated type specifies the concrete iterator type that will be generated by `into_iter`. This allows different collections to define their own specialized and efficient iterator types. -- **Associated Implementation `Iterator: Iterator`**: This is the core concept. It establishes a contract at the trait level, ensuring that the type specified by `IntoIter` must itself implement the `Iterator` trait. This binding is enforced across all implementations of `IntoIterator`. +## Paths for Referring to Items -### Benefits of Associated Implementations +To reference code within modules, Cairo uses paths, similar to filesystem navigation. Paths consist of identifiers separated by double colons (`::`). -This design pattern provides significant advantages: +There are two forms of paths: -- **Type-Safe Iteration**: Guarantees that the `into_iter` method will always return a type that conforms to the `Iterator` trait, enabling type-safe iteration without explicit type annotations. -- **Code Ergonomics**: Simplifies the iteration process by abstracting away the specific iterator type. +- **Absolute Path**: Starts from the crate root, beginning with the crate name. +- **Relative Path**: Starts from the current module. -### Example Implementation +### Example: Absolute and Relative Paths -The following code illustrates these traits with `ArrayIter` as the collection type: +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: -```cairo, noplayground -// Collection type that contains a simple array -#[derive(Drop)] -pub struct ArrayIter { - array: Array, -} +Filename: src/lib.cairo -// T is the collection type -pub trait Iterator { - type Item; - fn next(ref self: T) -> Option; -} +```cairo,noplayground +mod front_of_house { + mod hosting { + fn add_to_waitlist() {} + fn seat_at_table() {} + } -impl ArrayIterator of Iterator> { - type Item = T; - fn next(ref self: ArrayIter) -> Option { - self.array.pop_front() + mod serving { + fn take_order() {} + fn serve_order() {} + fn take_payment() {} } } -/// Turns a collection of values into an iterator -pub trait IntoIterator { - /// The iterator type that will be created - type IntoIter; - impl Iterator: Iterator; - - fn into_iter(self: T) -> Self::IntoIter; -} +pub fn eat_at_restaurant() { + // Absolute path + crate::front_of_house::hosting::add_to_waitlist(); -impl ArrayIntoIterator of IntoIterator> { - type IntoIter = ArrayIter; - fn into_iter(self: Array) -> ArrayIter { - ArrayIter { array: self } - } + // Relative path + front_of_house::hosting::add_to_waitlist(); } ``` -Verification and Debugging +- 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`. -# Verification and Debugging +## Starting Relative Paths with `super` -Error Handling in Cairo +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. -Introduction to Error Handling in Cairo +### Example: Using `super` -# Introduction to Error Handling in Cairo +In the following example, `fix_incorrect_order` within the `back_of_house` module calls `deliver_order` from its parent module: -Unrecoverable Errors: Panic and Related Concepts - -# Unrecoverable Errors: Panic and Related Concepts +Filename: src/lib.cairo -In Cairo, unrecoverable errors are handled using the `panic` mechanism, which terminates the program's execution. Panics can be triggered intentionally by calling the `panic` function or inadvertently through runtime errors like out-of-bounds array access. When a panic occurs, the program unwinds, dropping variables and squashing dictionaries to ensure a safe termination. +```cairo,noplayground +fn deliver_order() {} -## Triggering Panics +mod back_of_house { + fn fix_incorrect_order() { + cook_order(); + super::deliver_order(); + } -There are several ways to trigger a panic in Cairo: + fn cook_order() {} +} +``` -### The `panic` Function +Here, `super::deliver_order()` references the `deliver_order` function defined in the module containing `back_of_house`. -The `panic` function from the core library can be used to explicitly halt execution and signal an error. It accepts an array of `felt252` elements, which can convey error information. +Visibility and Privacy Rules -```cairo -use core::panic; +# Visibility and Privacy Rules -#[executable] -fn main() { - let mut data = array![2]; // Example error code +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 true { - panic(data); - } - println!("This line isn't reached"); -} -``` +## Modules and Privacy -Executing this code results in: +- **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. -```shell -$ 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 5 seconds - Executing no_listing_01_panic -error: Panicked with 0x2. +For example, consider a crate structure: +```text +backyard/ +├── Scarb.toml +└── src + ├── garden + │ └── vegetables.cairo + ├── garden.cairo + └── lib.cairo ``` -### `core::panic_with_felt252` - -This function provides a more concise way to panic with a single `felt252` error message. +The crate root `src/lib.cairo` might contain: ```cairo -use core::panic_with_felt252; +pub mod garden; +use crate::garden::vegetables::Asparagus; #[executable] fn main() { - panic_with_felt252(2); // Panics with error code 2 + let plant = Asparagus {}; + println!("I'm growing {:?}!", plant); } ``` -This yields the same error output as the `panic` function. +The `garden` module, defined in `src/garden.cairo`, would be declared as: -### The `panic!` Macro +```cairo,noplayground +pub mod vegetables; +``` -The `panic!` macro offers a convenient syntax for panicking, especially when a simple string message is sufficient. It can accept string literals longer than 31 bytes, unlike `panic_with_felt252`. +And `src/garden/vegetables.cairo` would contain: -```cairo -#[executable] -fn main() { - if true { - panic!("2"); // Panics with the string "2" - } - println!("This line isn't reached"); -} +```cairo,noplayground +#[derive(Drop, Debug)] +pub struct Asparagus {} ``` -A longer error message is also possible: +The `use crate::garden::vegetables::Asparagus;` line in `lib.cairo` brings the `Asparagus` type into scope. -```cairo, noplayground -panic!("the error for panic! macro is not limited to 31 characters anymore"); -``` +## Exposing Paths with the `pub` Keyword -## `nopanic` Notation +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`. -The `nopanic` notation can be used to declare that a function is guaranteed not to panic. Only functions marked with `nopanic` can be called within another `nopanic` function. +Consider a scenario where `eat_at_restaurant` needs to call `add_to_waitlist` from a nested module: ```cairo,noplayground -fn function_never_panic() -> felt252 nopanic { - 42 // This function is guaranteed to return 42 and never panic. +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} + } +} + +pub fn eat_at_restaurant() { + // Absolute path + crate::front_of_house::hosting::add_to_waitlist(); // Compiles + + // Relative path + front_of_house::hosting::add_to_waitlist(); // Compiles } ``` -Attempting to call a function that might panic from a `nopanic` function will result in a compile-time error: +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`. -```shell -$ scarb execute - Compiling no_listing_04_nopanic_wrong v0.1.0 (listings/ch09-error-handling/no_listing_05_nopanic_wrong/Scarb.toml) -error: Function is declared as nopanic but calls a function that may panic. - --> listings/ch09-error-handling/no_listing_05_nopanic_wrong/src/lib.cairo:4:12 - assert(1 == 1, 'what'); - ^^^^^^ +## `use` Keyword for Shortcuts + +The `use` keyword creates shortcuts to items, reducing the need to repeat long paths. For instance, `use crate::garden::vegetables::Asparagus;` allows you to refer to `Asparagus` directly within its scope. + +## Example: Private Struct Fields -error: Function is declared as nopanic but calls a function that may panic. - --> listings/ch09-error-handling/no_listing_05_nopanic_wrong/src/lib.cairo:4:5 - assert(1 == 1, 'what'); - ^^^^^^^^^^^^^^^^^^^^^^ +While a struct can be public, its fields remain private by default. -error: could not compile `no_listing_04_nopanic_wrong` due to previous error -error: `scarb metadata` exited with error +```cairo +pub mod rectangle { + #[derive(Copy, Drop)] + pub struct Rectangle { + width: u64, // Private field + height: u64 // Private field + } +} +fn main() { + // This would not compile because width and height are private: + // let r = rectangle::Rectangle { width: 10, height: 20 }; + // println!("{}", r.width); +} ``` -Note that `assert` and equality checks (`==`) can themselves cause panics. +To make the fields accessible, they would also need to be marked with `pub`. -## `panic_with` Attribute +Using the `use` Keyword for Imports and Re-exports -The `#[panic_with]` attribute can be applied to functions returning `Option` or `Result`. It generates a wrapper function that panics with a specified reason if the original function returns `None` or `Err`. +# Using the `use` Keyword for Imports and Re-exports + +The `use` keyword allows you to bring paths into the current scope, creating shorter and less repetitive ways to refer to items. This is similar to creating a symbolic link. The `use` statement is scoped to the block in which it appears. + +## Bringing Paths into Scope with the `use` Keyword + +To simplify calls to functions that are deep within modules, you can bring their module into scope. For example, `use crate::front_of_house::hosting;` allows you to call `hosting::add_to_waitlist()` instead of the full path. ```cairo -#[panic_with('value is 0', wrap_not_zero)] -fn wrap_if_not_zero(value: u128) -> Option { - if value == 0 { - None - } else { - Some(value) +// section "Defining Modules to Control Scope" + +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} } } +use crate::front_of_house::hosting; -#[executable] -fn main() { - wrap_if_not_zero(0); // This returns None - wrap_not_zero(0); // This panics with 'value is 0' +pub fn eat_at_restaurant() { + hosting::add_to_waitlist(); // ✅ Shorter path } ``` -Recoverable Errors: The Result Type +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. -# Recoverable Errors: The Result Type +## Creating Idiomatic `use` Paths -Most errors in programming are not severe enough to warrant program termination. In such cases, functions can return an error or a wrapped result instead of causing undefined behavior or halting the process. Cairo uses the `Result` enum for this purpose. +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. -## The `Result` Enum +Conversely, when bringing types like structs, enums, or traits into scope, it's idiomatic to specify the full path to the item itself. -The `Result` enum, defined with two variants, `Ok` and `Err`, is used to represent operations that may either succeed or fail. +```cairo +use core::num::traits::BitSize; -```cairo,noplayground -enum Result { - Ok: T, - Err: E, +#[executable] +fn main() { + let u8_size: usize = BitSize::::bits(); + println!("A u8 variable has {} bits", u8_size) } ``` -Here, `T` represents the type of the value returned on success, and `E` represents the type of the error value returned on failure. +### Providing New Names with the `as` Keyword -## The `ResultTrait` +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. -The `ResultTrait` trait provides essential methods for interacting with `Result` values. These include methods for extracting values, checking the variant, and handling panics. +```cairo +use core::array::ArrayTrait as Arr; -```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; +#[executable] +fn main() { + let mut arr = Arr::new(); // ArrayTrait was renamed to Arr + arr.append(1); } ``` -- `expect(err)` and `unwrap()`: Both return the value within `Ok`. `expect` allows a custom panic message, while `unwrap` uses a default one. -- `expect_err(err)` and `unwrap_err()`: Both return the value within `Err`. `expect_err` allows a custom panic message, while `unwrap_err` uses a default one. -- `is_ok()`: Returns `true` if the `Result` is `Ok`. -- `is_err()`: Returns `true` if the `Result` is `Err`. +### Importing Multiple Items from the Same Module + +To import several items from the same module cleanly, you can use curly braces `{}` to list them. -The `<+Drop>` and `<+Drop>` syntax denotes generic type constraints, requiring a `Drop` trait implementation for the types `T` and `E`. +```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, + } -## Using `Result` for Error Handling + #[derive(Drop)] + pub struct Circle { + pub radius: u32, + } -Functions can return a `Result` type, allowing callers to handle success or failure using pattern matching. + #[derive(Drop)] + pub struct Triangle { + pub base: u32, + pub height: u32, + } +} -For example, `u128_overflowing_add` returns a `Result`: +// We can import the structures `Square`, `Circle`, and `Triangle` from the `shapes` module like +// this: +use shapes::{Circle, Square, Triangle}; -```cairo,noplayground -fn u128_overflowing_add(a: u128, b: u128) -> Result; +// 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 }; + // ... +} ``` -This function returns `Ok(sum)` if the addition is successful, and `Err(overflowed_value)` if it overflows. +## Re-exporting Names in Module Files -A function like `u128_checked_add` can convert this `Result` to an `Option`: +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,noplayground -fn u128_checked_add(a: u128, b: u128) -> Option { - match u128_overflowing_add(a, b) { - Ok(r) => Some(r), - Err(r) => None, +```cairo +mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} } } -``` -Another example is `parse_u8`, which converts a `felt252` to a `u8`: +pub use crate::front_of_house::hosting; -```cairo,noplayground -fn parse_u8(s: felt252) -> Result { - match s.try_into() { - Some(value) => Ok(value), - None => Err('Invalid integer'), - } +fn eat_at_restaurant() { + hosting::add_to_waitlist(); } ``` -## The `?` Operator +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 `?` operator provides a concise way to handle recoverable errors. If an expression returns a `Result` and the result is `Err`, the `?` operator immediately returns the error from the current function. If the result is `Ok`, it unwraps the value and continues execution. +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 -fn do_something_with_parse_u8(input: felt252) -> Result { - let input_to_u8: u8 = parse_u8(input)?; // Propagates error if parse_u8 fails - // DO SOMETHING - let res = input_to_u8 - 1; - Ok(res) +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() {} + } } ``` -This operator simplifies error propagation, making the code cleaner by allowing the caller to manage errors. +Listing 7-1: A `front_of_house` module containing other modules that then contain functions -Common Error Messages and Resolutions +This structure creates a module tree where `hosting` and `serving` are children of `front_of_house`, and siblings to each other. -# Common Error Messages and Resolutions +### Module Tree Representation -This section lists common error messages encountered in Cairo and provides guidance on how to resolve them. +The module tree visually represents the relationships between modules: -## Variable Management Errors +```text +restaurant + └── front_of_house + ├── hosting + │ ├── add_to_waitlist + │ └── seat_at_table + └── serving + ├── take_order + ├── serve_order + └── take_payment +``` -- **`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 destroyed. +Listing 7-2: The module tree for the code in Listing 7-1 - - **Resolution**: Ensure that variables requiring destruction at the end of a function's execution implement either the `Drop` trait or the `Destruct` trait. Refer to the [Ownership](ch04-01-what-is-ownership.md#destroying-values---example-with-feltdict) section for more details. +This organization is analogous to a file system's directory structure, allowing for logical grouping and easy navigation of code. -- **`Variable was previously moved.`**: This error indicates that you are attempting to use a variable whose ownership has already been transferred to another function. When a variable does not implement the `Copy` trait, it is passed by value, transferring ownership. - - **Resolution**: Use the `clone` method if you need to use the variable's value after its ownership has been transferred. +## Separating Modules into Files -## Type and Trait Errors +As modules grow, it's beneficial to move their definitions into separate files to maintain code clarity and organization. -- **`error: Trait has no implementation in context: core::fmt::Display::`**: This error arises when trying to print an instance of a custom data type using `{}` placeholders in `print!` or `println!` macros. +### Extracting a Module to a New File - - **Resolution**: Manually implement the `Display` trait for your type, or apply `derive(Debug)` to your type and use `{:?}` placeholders to print the instance. +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. -- **`Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.`**: This error signifies that an entrypoint was called without the expected arguments. +**Example:** Moving `front_of_house` from `src/lib.cairo`: - - **Resolution**: Verify that the arguments provided when calling an entrypoint are correct. For `u256` variables (which are structs of two `u128`), you need to pass two values when calling a function that expects a `u256`. +Filename: src/lib.cairo -- **`Item path::item is not visible in this context.`**: This error means the path to an item is correct, but there's a visibility issue. By default, items are private to their parent modules. +```cairo,noplayground +mod front_of_house; +use crate::front_of_house::hosting; - - **Resolution**: Declare the item and all modules in its path with `pub(crate)` or `pub` to grant access. +fn eat_at_restaurant() { + hosting::add_to_waitlist(); +} +``` -- **`Identifier not found.`**: This is a general error that might indicate: - - A variable is used before declaration. - - An incorrect path is used to bring an item into scope. - - **Resolution**: Ensure variables are declared using `let` before use and that paths to items are valid. +Listing 7-14: Declaring the `front_of_house` module whose body will be in `src/front_of_house.cairo` -## Starknet Components Related Error Messages +Filename: src/front_of_house.cairo -- **`Trait not found. Not a trait.`**: This error can occur if a component's `impl` block is not imported correctly in your contract. +```cairo,noplayground +pub mod hosting { + pub fn add_to_waitlist() {} +} +``` - - **Resolution**: Ensure you follow the correct syntax for importing component implementations: +Listing 7-15: Definitions inside the `front_of_house` module in `src/front_of_house.cairo` - ```cairo,noplayground - #[abi(embed_v0)] - impl IMPL_NAME = PATH_TO_COMPONENT::EMBEDDED_NAME - ``` +### Extracting a Child Module -Testing in Cairo +To extract a child module (e.g., `hosting` within `front_of_house`): -Introduction to Cairo Testing +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. -# Introduction to Cairo Testing +**Example:** Moving `hosting` into its own file: -Writing and Running Basic Tests +Filename: src/front_of_house.cairo -# Writing and Running Basic Tests +```cairo,noplayground +pub mod hosting; +``` -A test in Cairo is a function annotated with the `#[test]` attribute. This attribute signals to the test runner that the function should be executed as a test. +Filename: src/front_of_house/hosting.cairo -## Project Setup and Running Tests +```cairo,noplayground +pub fn add_to_waitlist() {} +``` -To begin, create a new project using Scarb: +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. -```shell -scarb new adder -``` +Advanced Topics: Traits and Component Integration -This creates a basic project structure: +# Advanced Topics: Traits and Component Integration -``` -adder -├── Scarb.toml -└── src - └── lib.cairo -``` +## Module Organization and Trait Implementation -The `scarb test` command is used to compile and run all tests within the project. +When organizing code into modules, if a trait's implementation resides in a different module than the trait itself, explicit imports are necessary. -## Defining a Test Function +```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; +} -A simple test function is defined by adding the `#[test]` attribute before the `fn` keyword. For example: +mod rectangle { + // Importing ShapeGeometry is required to implement this trait for Rectangle + use super::ShapeGeometry; -Filename: src/lib.cairo + #[derive(Copy, Drop)] + pub struct Rectangle { + pub height: u64, + pub width: u64, + } -```cairo, noplayground -pub fn add(left: usize, right: usize) -> usize { - left + right + // 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 + } + } } -#[cfg(test)] -mod tests { - use super::*; +mod circle { + // Importing ShapeGeometry is required to implement this trait for Circle + use super::ShapeGeometry; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + #[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 +} ``` -Listing 10-1: A simple test function +## Contract Integration -The `#[cfg(test)]` attribute ensures that the `tests` module and its contents are only compiled when running tests. The `use super::*;` line brings items from the parent module into the scope of the `tests` module. +Contracts integrate components using **impl aliases** to instantiate a component's generic impl with the contract's concrete state. -## Assertions in Tests +```cairo,noplayground + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; -Tests often use assertion macros like `assert_eq!` to verify expected outcomes. `assert_eq!(a, b)` checks if `a` is equal to `b`. + impl OwnableInternalImpl = OwnableComponent::InternalImpl; +``` -## Test Execution Output +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. -When `scarb test` is run, it compiles and executes the annotated test functions. The output indicates which tests are running, their status (e.g., `[PASS]`), and a summary of the results. +## Specifying Component Dependencies -```shell -$ scarb test - Running test listing_10_01 (snforge test) - Compiling test(listings/ch10-testing-cairo-programs/listing_10_01/Scarb.toml) - Finished `dev` profile target(s) in 8 seconds -[WARNING] File = /Users/msaug/workspace/cairo-book/listings/ch10-testing-cairo-programs/listing_10_01/target/dev/listing_10_01_unittest.test.starknet_artifacts.json missing when it should be existing, perhaps due to Scarb problem. +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. -Collected 2 test(s) from listing_10_01 package -Running 2 test(s) from src/ -[PASS] listing_10_01::tests::it_works (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -[PASS] listing_10_01::other_tests::exploration (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out -``` +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. -## Renaming Test Functions +Macros, Attributes, and Compiler Internals -Test function names can be changed to be more descriptive. For instance, renaming `it_works` to `exploration`: +Cairo Attributes -```cairo, noplayground - #[test] - fn exploration() { - let result = 2 + 2; - assert_eq!(result, 4); - } -``` +# Cairo Attributes -Running `scarb test` again will show the new name in the output. +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. -## Filtering Tests +## Core Attributes -It's possible to run only specific tests by passing their names (or parts of their names) as arguments to `scarb test`. For example, `scarb test add_two` would run tests whose names contain "add_two". Tests that do not match the filter are reported as "filtered out". +- **`#[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. -Assertion Macros in Cairo +## Contract ABI Attributes -# Assertion Macros in Cairo +- **`#[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 provides several macros to help assert conditions during testing, ensuring code behaves as expected. + ```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) {} + } + } + ``` -## The `assert!` Macro +- **`#[external(v0)]`**: Used to explicitly define a function as external when the `#[abi(per_item)]` attribute is applied to the implementation block. -The `assert!` macro is used to verify that a condition evaluates to `true`. If the condition is `false`, the macro causes the test to fail by calling `panic()` with a specified message. +## Event Attributes -Consider the `Rectangle` struct and its `can_hold` method: +- **`#[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. -Filename: src/lib.cairo +Cairo Macros -```cairo, noplayground -#[derive(Drop)] -struct Rectangle { - width: u64, - height: u64, -} +# Cairo Macros -trait RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool; -} +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. -impl RectangleImpl of RectangleTrait { - fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { - *self.width > *other.width && *self.height > *other.height - } -} -``` +## The Difference Between Macros and Functions -Listing 10-3: Using the `Rectangle` struct and its `can_hold` method from Chapter 5 +While both macros and functions help reduce code duplication, macros offer additional flexibility: -A test using `assert!` can verify the `can_hold` method: +- **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. -```cairo, noplayground -# #[derive(Drop)] -# struct Rectangle { -# width: u64, -# height: u64, -# } +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 + } + }; +} # -# trait RectangleTrait { -# fn can_hold(self: @Rectangle, other: @Rectangle) -> bool; +# #[cfg(test)] +# #[test] +# fn test_make_array() { +# let a = make_array![1, 2, 3]; +# let expected = array![1, 2, 3]; +# assert_eq!(a, expected); # } # -# impl RectangleImpl of RectangleTrait { -# fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { -# *self.width > *other.width && *self.height > *other.height +# 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!() +# } +# }; # } # } # -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn larger_can_hold_smaller() { - let larger = Rectangle { height: 7, width: 8 }; - let smaller = Rectangle { height: 1, width: 5 }; - - assert!(larger.can_hold(@smaller), "rectangle cannot hold"); - } -} +# 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); # } +# +# ``` -## `assert_eq!` and `assert_ne!` Macros - -These macros are convenient for testing equality (`assert_eq!`) and inequality (`assert_ne!`) between two values. They provide more detailed failure messages than `assert!` by printing the values being compared. +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);`. -The `add_two` function and its tests: +### Hygiene, `$defsite`/`$callsite`, and `expose!` -Filename: src/lib.cairo +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::`). -```cairo, noplayground -pub fn add_two(a: u32) -> u32 { - a + 2 -} +Macros are expected to expand to a single expression. If a macro defines multiple statements, they should be wrapped in a `{}` block. -#[cfg(test)] -mod tests { - use super::*; +An end-to-end example demonstrating these concepts: - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2)); +```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 } - #[test] - fn wrong_check() { - assert_ne!(0, add_two(2)); + // Adds the defsite bonus, regardless of what exists at the callsite + pub macro add_defsite_bonus { + ($x: expr) => { $x + $defsite::def_bonus() }; } -} -``` -Listing 10-5: Testing the function `add_two` using `assert_eq!` and `assert_ne!` macros + // Adds the callsite bonus, resolved where the macro is invoked + pub macro add_callsite_bonus { + ($x: expr) => { $x + $callsite::bonus() }; + } -When an assertion fails, these macros display the `left` and `right` values. For these macros to work, the compared types must implement the `PartialEq` and `Debug` traits. Deriving these traits using `#[derive(PartialEq, Debug)]` is usually sufficient. + // 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;); + }; + } -Example with structs: + // A helper macro that reads a callsite-exposed variable + pub macro read_exposed_total { + () => { $callsite::exposed_total }; + } -```cairo, noplayground -#[derive(Drop, Debug, PartialEq)] -struct MyStruct { - var1: u8, - var2: u8, + // 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); +# } +# +# +``` -#[cfg(test)] -#[test] -fn test_struct_equality() { - let first = MyStruct { var1: 1, var2: 2 }; - let second = MyStruct { var1: 1, var2: 2 }; - let third = MyStruct { var1: 1, var2: 3 }; +### Enabling Inline Macros - assert_eq!(first, second); - assert_eq!(first, second, "{:?},{:?} should be equal", first, second); - assert_ne!(first, third); - assert_ne!(first, third, "{:?},{:?} should not be equal", first, third); -} +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"] ``` -## `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` Macros +## Procedural Macros -These macros facilitate comparison tests: +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]`. -- `assert_lt!`: Checks if the left value is strictly less than the right value. -- `assert_le!`: Checks if the left value is less than or equal to the right value. -- `assert_gt!`: Checks if the left value is strictly greater than the right value. -- `assert_ge!`: Checks if the left value is greater than or equal to the right value. +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]`. -These macros require the types to implement comparison traits like `PartialOrd`. The `Dice` struct example demonstrates this: +### Expression Macros -```cairo, noplayground -#[derive(Drop, Copy, Debug, PartialEq)] -struct Dice { - number: u8, -} +Expression macros transform Cairo expressions. An example is a compile-time power function (`pow!`) implemented using Rust crates like `cairo-lang-macro` and `bigdecimal`. -impl DicePartialOrd of PartialOrd { - fn lt(lhs: Dice, rhs: Dice) -> bool { - lhs.number < rhs.number - } +### Derive Macros - fn le(lhs: Dice, rhs: Dice) -> bool { - lhs.number <= rhs.number - } +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. - fn gt(lhs: Dice, rhs: Dice) -> bool { - lhs.number > rhs.number - } +### Attribute Macros - fn ge(lhs: Dice, rhs: Dice) -> bool { - lhs.number >= rhs.number - } -} +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. -#[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 }; +## Common Macros - 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, - ); -} -``` +Several built-in macros are available for common tasks: -Listing 10-6: Example of tests that use the `assert_xx!` macros for comparisons +| 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. | -Note that the `Dice` struct also derives `Copy` to allow multiple uses in comparisons. +Comments are also supported using `//` for line comments. -## Adding Custom Failure Messages +Compiler Internals and Optimizations -Optional arguments can be passed to assertion macros to provide custom failure messages. These arguments are processed by the `format!` macro, allowing for formatted strings with placeholders. +# Inlining in Cairo -Example with a custom message for `assert_eq!`: +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. -```cairo, noplayground - #[test] - fn it_adds_two() { - assert_eq!(4, add_two(2), "Expected {}, got add_two(2)={}", 4, add_two(2)); - } -``` +## The `inline` Attribute -This results in a more informative error message upon test failure, detailing the expected versus actual values. +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. -Handling Test Failures and Panics +> Inlining is often a tradeoff between the number of steps and code length. Use the `inline` attribute cautiously where it is appropriate. -# Handling Test Failures and Panics +## Inlining Decision Process -Tests fail when the code being tested panics. Each test runs in its own thread, and if that thread dies, the test is marked as failed. +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. -## Making Tests Fail +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. -You can create a test that fails by using assertion macros like `assert!` with a condition that is false. For example, `assert!(result == 6, "Make this test fail")` will cause the test to fail with the provided message. +## Inlining Example -When a test fails, the output will indicate `[FAIL]` for that specific test, followed by a "Failure data" section detailing the reason for the failure, often including the panic message. +Consider the following program demonstrating inlining: -```cairo, noplayground -#[cfg(test)] -mod tests { - #[test] - fn exploration() { - let result = 2 + 2; - assert_eq!(result, 4); - } +```cairo +#[executable] +fn main() -> felt252 { + inlined() + not_inlined() +} - #[test] - fn another() { - let result = 2 + 2; - assert!(result == 6, "Make this test fail"); - } +#[inline(always)] +fn inlined() -> felt252 { + 1 +} + +#[inline(never)] +fn not_inlined() -> felt252 { + 2 } ``` -## Checking for Panics with `should_panic` +Listing 12-5: A small Cairo program that adds the return value of 2 functions, with one of them being inlined -To verify that your code handles error conditions correctly by panicking, you can use the `#[should_panic]` attribute on a test function. This test will pass if the code within the function panics, and fail if it does not. +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. -Consider a `Guess` struct where the `new` function panics if the input value is out of range: +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. -```cairo, noplayground -#[derive(Drop)] -struct Guess { - value: u64, -} +## Summary -pub trait GuessTrait { - fn new(value: u64) -> Guess; -} +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. -impl GuessImpl of GuessTrait { - fn new(value: u64) -> Guess { - if value < 1 || value > 100 { - panic!("Guess must be >= 1 and <= 100"); - } - Guess { value } - } -} -``` +Testing and Debugging -A test for this would look like: +Introduction to Cairo Testing and Debugging -```cairo, noplayground -#[cfg(test)] -mod tests { - use super::*; +# Introduction to Cairo Testing and Debugging - #[test] - #[should_panic] - fn greater_than_100() { - GuessTrait::new(200); - } -} -``` +## Executing the Program + +To test a Cairo program, you can use the `scarb execute` command. This command runs the program and displays its output. -If the `new` function is modified to not panic when it should, the `#[should_panic]` test will fail. +**Example:** Executing a primality test program with the input `17`. -## Making `should_panic` More Precise +```bash +scarb execute -p prime_prover --print-program-output --arguments 17 +``` -The `#[should_panic]` attribute can be made more precise by providing an `expected` parameter, which specifies a substring that must be present in the panic message. +- `-p prime_prover`: Specifies the package name. +- `--print-program-output`: Displays the program's result. +- `--arguments 17`: Passes `17` as input to the program. -For example, if the `new` function panics with different messages for out-of-bounds values: +The output indicates success (0 for no panic) and the program's result (1 for true, meaning 17 is prime). -```cairo, noplayground -impl GuessImpl of GuessTrait { - fn new(value: u64) -> Guess { - if value < 1 { - panic!("Guess must be >= 1"); - } else if value > 100 { - panic!("Guess must be <= 100"); - } - Guess { value } - } -} +```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 ``` -A precise test would be: +Try with other numbers: -```cairo, noplayground -#[cfg(test)] -mod tests { - use super::*; +```bash +$ scarb execute -p prime_prover --print-program-output --arguments 4 +[0, 0] # 4 is not prime - #[test] - #[should_panic(expected: "Guess must be <= 100")] - fn greater_than_100() { - GuessTrait::new(200); - } -} +$ scarb execute -p prime_prover --print-program-output --arguments 23 +[0, 1] # 23 is prime ``` -If the panic message does not match the `expected` string, the test will fail, indicating the mismatch between the actual and expected panic messages. +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. -Advanced Testing Features (Ignoring, Specific Tests) +## Generating a Zero-Knowledge Proof -### Ignoring Specific Tests +Cairo 2.10 integrates the Stwo prover via Scarb, enabling direct generation of zero-knowledge proofs. -To exclude time-consuming tests from regular runs of `scarb test`, you can use the `#[ignore]` attribute. This attribute is placed above the `#[test]` attribute for the test function you wish to exclude. +To generate a proof for a specific execution (e.g., `execution1`), use the `scarb prove` command: -```cairo, noplayground -pub fn add(left: usize, right: usize) -> usize { - left + right -} +```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 +``` -#[cfg(test)] -mod tests { - use super::*; +This command generates a proof that the primality check was computed correctly without revealing the input. - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +Understanding and Resolving Cairo Errors - #[test] - #[ignore] - fn expensive_test() { // code that takes an hour to run - } -} +# 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.` + +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. + +### `error: Trait has no implementation in context: core::fmt::Display::` + +This error arises when trying to print an instance of a custom data type using `{}` placeholders with `print!` or `println!`. To fix this, either manually implement the `Display` trait for your type or derive the `Debug` trait (using `#[derive(Debug)]`) and use `:?` placeholders for printing. + +### `Got an exception while executing a hint: Hint Error: Failed to deserialize param #x.` + +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. + +### `Item path::item is not visible in this context.` + +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. + +### `Identifier not found.` + +This is a general error that can indicate: + +- 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. + +## Starknet Components Related Error Messages + +### `Trait not found. Not a trait.` + +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: + +```cairo,noplayground +#[abi(embed_v0)] +impl IMPL_NAME = PATH_TO_COMPONENT::EMBEDDED_NAME ``` -When `scarb test` is executed, tests marked with `#[ignore]` will not be run. +Testing Fundamentals: Writing and Running Tests -Organizing Tests: Unit vs. Integration +# Testing Fundamentals: Writing and Running Tests -# Organizing Tests: Unit vs. Integration +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. -Tests in Cairo can be broadly categorized into two main types: unit tests and integration tests. This distinction helps in systematically verifying the correctness of code, both in isolation and when components interact. +## The Purpose of Tests -## Unit Tests +Tests are Cairo functions designed to verify that other code functions as intended. A typical test function involves three steps: -Unit tests are designed to test individual units of code, such as functions or modules, in isolation. This allows for quick identification of issues within specific components. +1. **Set up**: Prepare any necessary data or state. +2. **Run**: Execute the code being tested. +3. **Assert**: Verify that the results match expectations. -### Location and Structure +## Anatomy of a Test Function -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)]`. +Cairo offers several features for writing tests: -The `#[cfg(test)]` attribute instructs Cairo to compile and run this code only when the `scarb test` command is executed, not during a regular build (`scarb build`). This prevents test code from being included in the final compiled artifact, saving compile time and reducing the artifact's size. +- `#[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. -**Example Unit Test:** +### The `#[test]` Attribute -```cairo -pub fn add(left: usize, right: usize) -> usize { - left + right +A function annotated with `#[test]` is recognized by the test runner. The `scarb test` command executes these functions. + +```cairo,noplayground +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); // Assuming 'add' is a function in the outer scope + assert_eq!(result, 4); + } } +``` + +### The `#[cfg(test)]` Attribute +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. + +```cairo,noplayground #[cfg(test)] mod tests { use super::*; @@ -6249,11 +5889,10 @@ mod tests { ### Testing Private Functions -Cairo's privacy rules allow unit tests to access and test private functions. Since the `tests` module is a child module of the code it's testing, it can use items from its parent modules. +Cairo allows testing private functions. Items within child modules (like the `tests` module) can access items in their ancestor modules. -**Example of Testing a Private Function:** - -```cairo +```cairo,noplayground +// Filename: src/lib.cairo pub fn add(a: u32, b: u32) -> u32 { internal_adder(a, 2) } @@ -6264,3533 +5903,225 @@ fn internal_adder(a: u32, b: u32) -> u32 { #[cfg(test)] mod tests { - use super::*; + use super::*; // Brings internal_adder into scope #[test] - fn add() { + fn test_private_function() { + // The test can call the private function directly assert_eq!(4, internal_adder(2, 2)); } } ``` -In this example, `internal_adder` is a private function. The `tests` module uses `use super::internal_adder;` to bring it into scope and test it. - -## Integration Tests - -Integration tests focus on verifying how different modules or components of your code work together. They treat your code as an external user would, interacting with it through its public interface. - -### Location and Execution - -Integration tests are placed in a separate directory, typically named `tests/`, outside of the `src` directory. Because they are in a different location, they do not require the `#[cfg(test)]` attribute. - -To run integration tests, you use the `scarb test` command, often with a filter to target specific test modules within the `tests/` directory. - -Running Integration Tests - -# Running Integration Tests - -Integration tests verify that different parts of your library work together correctly. They are structured similarly to unit tests but are placed in a `tests` directory at the top level of your project, alongside the `src` directory. - -### The `tests` Directory - -Scarb automatically looks for integration tests in the `tests` directory. Each file within this directory is compiled as an individual crate. To create an integration test, create a `tests` directory and a file (e.g., `integration_tests.cairo`) within it. - -**Directory Structure Example:** - -```shell -adder -├── Scarb.lock -├── Scarb.toml -├── src -│ └── lib.cairo -└── tests - └── integration_tests.cairo -``` - -**Example `tests/integration_tests.cairo`:** - -```cairo, noplayground -use adder::add_two; - -#[test] -fn it_adds_two() { - assert_eq!(4, add_two(2)); -} -``` - -In integration tests, you need to bring your library's functions into scope using `use`, unlike unit tests which can use `super`. +## Running Tests -### Running Integration Tests +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. -Run integration tests using the `scarb test` command. Scarb compiles files in the `tests` directory only when this command is executed. The output will show sections for both unit and integration tests. +Example output: ```shell $ scarb test - Running test adder (snforge test) - Blocking waiting for file lock on registry db cache - Blocking waiting for file lock on registry db cache - Compiling test(listings/ch10-testing-cairo-programs/no_listing_09_integration_test/Scarb.toml) - Compiling test(listings/ch10-testing-cairo-programs/no_listing_09_integration_test/Scarb.toml) - Finished `dev` profile target(s) in 19 seconds -[WARNING] File = /Users/msaug/workspace/cairo-book/listings/ch10-testing-cairo-programs/no_listing_09_integration_test/target/dev/adder_unittest.test.starknet_artifacts.json missing when it should be existing, perhaps due to Scarb problem. -[WARNING] File = /Users/msaug/workspace/cairo-book/listings/ch10-testing-cairo-programs/no_listing_09_integration_test/target/dev/adder_integrationtest.test.starknet_artifacts.json missing when it should be existing, perhaps due to Scarb problem. - - -Collected 2 test(s) from adder package -Running 1 test(s) from tests/ -[PASS] adder_integrationtest::integration_tests::it_adds_two (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -Running 1 test(s) from src/ -[PASS] adder::tests::internal (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) -Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out - -``` - -### Filtering Tests - -You can filter tests using the `-f` option with `scarb test`. To run a specific integration test function, use its full path (e.g., `scarb test -f integration_tests::internal`). To run all tests from a specific file, use the filename (e.g., `scarb test -f integration_tests`). - -### Submodules in Integration Tests - -To organize integration tests, you can create multiple files in the `tests` directory. Each file acts as a separate crate. If you need to share helper functions across test files, you can create a `tests/common.cairo` file and import its functions. However, each file in `tests` is compiled separately, unlike files in `src`. - -To have the `tests` directory behave as a single crate, create a `tests/lib.cairo` file that declares the other test files as modules: - -**Example `tests/lib.cairo`:** - -```cairo, noplayground -mod common; -mod integration_tests; +... +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 ``` -This setup allows helper functions to be imported and used without being tested themselves, and the output of `scarb test` will reflect this organization. - -Quizzes - -# Quizzes - -This section contains quizzes related to testing in Cairo. The quizzes cover topics such as identifying test annotations, writing tests for functions returning `Result`, and understanding test output. - -## Quiz: How to Write Tests - -This quiz assesses your understanding of writing tests in Cairo. It includes questions on: - -- The annotation used to mark a function as a test. -- Valid ways to test functions that return a `Result` type, specifically checking for an `Err` variant. -- The conditions under which a test with `#[should_panic]` passes, particularly concerning the expected panic message. -- Interpreting the output of `scarb cairo-test` when tests are filtered, ignored, or pass/fail. - -Smart Contracts on Starknet - -Introduction to Smart Contracts - -# Introduction to Smart Contracts - -This chapter provides a high-level introduction to smart contracts, their applications, and the reasons for using Cairo and Starknet. - -## Smart Contracts - Introduction - -Smart contracts are programs deployed on a blockchain, gaining prominence with Ethereum. They are essentially code and instructions that execute based on inputs. Key components include storage and functions. Users interact with them via blockchain transactions. Smart contracts have their own addresses and can hold tokens. - -Different blockchains use different programming languages: Solidity for Ethereum and Cairo for Starknet. Compilation also differs: Solidity compiles to bytecode, while Cairo compiles to Sierra and then Cairo Assembly (CASM). - -## Smart Contracts - Characteristics - -Smart contracts are: - -- **Permissionless**: Anyone can deploy them. -- **Transparent**: Stored data and code are publicly accessible. -- **Composable**: They can interact with other smart contracts. - -Smart contracts can only access blockchain-specific data; external data requires third-party software called oracles. Standards like ERC20 (for tokens) and ERC721 (for NFTs) facilitate interoperability between contracts. - -## Smart Contracts - Use Cases - -Smart contracts have diverse applications: - -### DeFi (Decentralized Finance) - -Enable financial applications without traditional intermediaries, including lending/borrowing, decentralized exchanges (DEXs), on-chain derivatives, and stablecoins. - -### Tokenization - -Facilitate the creation of digital tokens for real-world assets like real estate or art, enabling fractional ownership and easier trading. - -### Voting - -Create secure and transparent voting systems where votes are immutably recorded on the blockchain, with results tallied automatically. - -### Royalties - -Automate royalty payments for creators, distributing earnings automatically upon content sale or consumption. - -### Decentralized Identities (DIDs) - -Manage digital identities, allowing users to control personal information and securely share it, with contracts verifying authenticity and managing access. - -The use cases for smart contracts are continually expanding, with Starknet and Cairo playing a role in this evolution. - -Starknet and Scalability - -# Starknet and Scalability - -The success of Ethereum led to high transaction costs, necessitating solutions for scalability. The blockchain trilemma highlights the trade-off between scalability, decentralization, and security. Ethereum prioritizes decentralization and security, acting as a settlement layer, while complex computations are offloaded to Layer 2 (L2) networks. - -## Layer 2 Solutions +## Examples -L2s batch and compress transactions, compute new states, and settle results on Ethereum (L1). Two primary types exist: +### Testing Conversions -- **Optimistic Rollups:** New states are considered valid by default, with a 7-day challenge period for detecting malicious transactions. -- **Validity Rollups (e.g., Starknet):** Utilize cryptography, specifically STARKs, to cryptographically prove the correctness of computed states. This approach offers significantly higher scalability potential than optimistic rollups. - -Starkware's STARKs technology is a key enabler for Starknet's scalability. For a deeper understanding of Starknet's architecture, refer to the official [Starknet documentation](https://docs.starknet.io/documentation/architecture_and_concepts/). - -Cairo Smart Contract Example - -# Cairo Smart Contract Example - -## Dice Game Contract Overview - -This contract implements a dice game where players guess a number between 1 and 6. The contract owner can control the active game window. To determine winners, the owner requests a random number from the Pragma VRF oracle via `request_randomness_from_pragma`. The `receive_random_words` callback function stores this number. Players call `process_game_winners` to check if their guess matches the random number (modulo 6, plus 1), triggering `GameWinner` or `GameLost` events. - -## Code Example - -```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, - }; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - - #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; - impl InternalImpl = OwnableComponent::InternalImpl; +This example tests a `parse_u8` function that converts `felt252` to `u8`. - #[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, +```cairo,noplayground +fn parse_u8(s: felt252) -> Result { + match s.try_into() { + Some(value) => Ok(value), + None => Err('Invalid integer'), } +} - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - GameWinner: ResultAnnouncement, - GameLost: ResultAnnouncement, - #[flat] - OwnableEvent: OwnableComponent::Event, - } +#[cfg(test)] +mod tests { + use super::*; - #[derive(Drop, starknet::Event)] - struct ResultAnnouncement { - caller: ContractAddress, - guess: u8, - random_number: u256, + #[test] + fn test_felt252_to_u8() { + let number: felt252 = 5; + // should not panic + let res = parse_u8(number).unwrap(); } - #[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); + #[test] + #[should_panic] + fn test_felt252_to_u8_panic() { + let number: felt252 = 256; + // should panic + let res = parse_u8(number).unwrap(); } +} +``` - #[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'); - - let caller = get_caller_address(); - self.user_guesses.entry(caller).write(guess); - } +### Testing Struct Methods - fn toggle_play_window(ref self: ContractState) { - self.ownable.assert_only_owner(); +This tests a `can_hold` method on a `Rectangle` struct. - let current: bool = self.game_window.read(); - self.game_window.write(!current); - } +```cairo,noplayground +#[derive(Drop)] +struct Rectangle { + width: u64, + height: u64, +} - fn get_game_window(self: @ContractState) -> bool { - self.game_window.read() - } +trait RectangleTrait { + fn can_hold(self: @Rectangle, other: @Rectangle) -> bool; +} - 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, - }, - ), - ); - } - } +impl RectangleImpl of RectangleTrait { + fn can_hold(self: @Rectangle, other: @Rectangle) -> bool { + *self.width > *other.width && *self.height > *other.height } +} - #[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 - } - - 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(); - - 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::< - 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7, - >() // ETH Contract Address - }; - eth_dispatcher - .approve( - randomness_contract_address, - (callback_fee_limit + callback_fee_limit / 5).into(), - ); +#[cfg(test)] +mod tests { + use super::*; - // Request the randomness - randomness_dispatcher - .request_random( - seed, callback_address, callback_fee_limit, publish_delay, num_words, calldata, - ); + #[test] + fn larger_can_hold_smaller() { + let larger = Rectangle { height: 7, width: 8 }; + let smaller = Rectangle { height: 1, width: 5 }; - let current_block_number = get_block_number(); - self.min_block_number_storage.write(current_block_number + publish_delay); - } + assert!(larger.can_hold(@smaller), "rectangle cannot hold"); + } +} - 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'); +#[cfg(test)] +mod tests2 { + use super::*; - let random_word = *random_words.at(0); - self.last_random_number.write(random_word); - } + #[test] + fn smaller_cannot_hold_larger() { + let larger = Rectangle { height: 7, width: 8 }; + let smaller = Rectangle { height: 1, width: 5 }; - fn withdraw_extra_fee_fund(ref self: ContractState, receiver: ContractAddress) { - self.ownable.assert_only_owner(); - let eth_dispatcher = ERC20ABIDispatcher { - contract_address: contract_address_const::< - 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7, - >() // ETH Contract Address - }; - let balance = eth_dispatcher.balance_of(get_contract_address()); - eth_dispatcher.transfer(receiver, balance); - } + assert!(!smaller.can_hold(@larger), "rectangle cannot hold"); } } ``` -Deploying and Interacting with Starknet Contracts - -# Deploying and Interacting with Starknet Contracts - -Interacting with Starknet contracts involves deploying them and then either calling or invoking their functions. - -## Calling vs. Invoking Contracts - -- **Calling contracts:** Used for external functions that only read from the contract's state. These operations do not alter the network's state and therefore do not require fees or signing. -- **Invoking contracts:** Used for external functions that can write to the contract's state. These operations alter the network's state and require fees and signing. - -## Using the `katana` Local Starknet Node - -For local development and testing, `katana` is recommended. It allows you to perform all necessary Starknet operations locally. - -### Installing and Running `katana` - -1. **Installation:** Refer to the "Using Katana" chapter of the Dojo Engine for installation instructions. -2. **Version Check:** Ensure your `katana` version matches the specified version (e.g., `katana 1.0.9-dev (38b3c2a6)`). Upgrade if necessary using the same installation guide. - ```bash - $ katana --version - ``` -3. **Starting the Node:** Once installed, start the local Starknet node by running: - ```bash - katana - ``` - -You can also use the Goerli Testnet, but `katana` is preferred for local development. A complete tutorial for `katana` is available in the Starknet Docs' "Using a development network" chapter. - -Starknet Contract Fundamentals - -Introduction to Starknet and Cairo - -# Introduction to Starknet and Cairo - -## Cairo and Starknet +### Testing Starknet Contracts -Cairo is a language specifically designed for STARKs, enabling **provable code**. Starknet utilizes its own virtual machine (VM), diverging from competitors that use the EVM. This allows for greater flexibility and innovation. Key advantages include: - -- Reduced transaction costs. -- Native account abstraction for "Smart Accounts" and complex transaction flows. -- Support for emerging use cases like **transparent AI** and on-chain **blockchain games**. -- Optimized for STARK proof capabilities for enhanced scalability. - -## Cairo Programs vs. Starknet Contracts - -Starknet contracts are a superset of Cairo programs, meaning prior Cairo knowledge is applicable. - -A standard Cairo program requires a `main` function as its entry point: - -```cairo -fn main() {} -``` - -In contrast, Starknet contracts, which are executed by the sequencer and have access to Starknet's state, do not have a `main` function. Instead, they feature one or multiple functions that serve as entry points. - -Starknet Contract Structure and Core Attributes - -# Starknet Contract Structure and Core Attributes - -Starknet contracts are defined within modules annotated with the `#[starknet::contract]` attribute. - -## Anatomy of a Simple Contract - -A Starknet contract encapsulates state and logic within a module. The state is defined using a `struct` annotated with `#[storage]`, and the logic is implemented in functions that interact with this state. - -```cairo,noplayground -#[starknet::interface] -trait ISimpleStorage { - fn set(ref self: TContractState, x: u128); - fn get(self: @TContractState) -> u128; -} - -#[starknet::contract] -mod SimpleStorage { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - #[storage] - struct Storage { - stored_data: u128, - } - - #[abi(embed_v0)] - impl SimpleStorage of super::ISimpleStorage { - fn set(ref self: ContractState, x: u128) { - self.stored_data.write(x); - } - - fn get(self: @ContractState) -> u128 { - self.stored_data.read() - } - } -} -``` - -### Contract State - -The contract's state is defined within a `struct` annotated with `#[storage]`. This struct is initialized empty and holds the contract's persistent data. - -### Contract Interface - -Interfaces define the contract's public API. They are defined using traits annotated with `#[starknet::interface]`. Functions declared in an interface are public and callable from outside the contract. - -```cairo,noplayground -#[starknet::interface] -trait INameRegistry { - fn store_name(ref self: TContractState, name: felt252); - fn get_name(self: @TContractState, address: ContractAddress) -> felt252; -} -``` - -The `self` parameter in interface functions, often generic like `TContractState`, indicates that the function can access the contract's state. The `ref` keyword signifies that the state may be modified. - -### Constructor - -A contract can have only one constructor, named `constructor` and annotated with `#[constructor]`. It initializes the contract's state and can accept arguments for deployment. The `constructor` function must take `self` as its first argument, typically by reference (`ref`) to modify the state. - -### Public Functions - -Public functions are accessible externally. They can be defined within an `impl` block annotated with `#[abi(embed_v0)]` (implementing an interface) or as standalone functions with the `#[external(v0)]` attribute. - -Functions within `#[abi(embed_v0)]` implement the contract's interface and are entry points. Functions annotated with `#[external(v0)]` are also public. - -Both types of public functions must accept `self` as their first argument. - -#### External Functions - -External functions, specifically those where `self` is passed by reference (`ref self: ContractState`), grant both read and write access to storage variables, allowing state modification. - -Cairo Attributes and Function Signatures - -# Cairo Attributes and Function Signatures - -The following table outlines common Cairo attributes and their functionalities: - -| 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 for functions within an implementation. | -| `#[external(v0)]` | Defines an external function when `#[abi(per_item)]` is used. | -| `#[flat]` | Defines a non-nested `Event` enum variant, ignoring the variant name during serialization for composability. | -| `#[key]` | Defines an indexed `Event` enum field for more efficient event querying and filtering. | - -The following symbols are used in the context of calling or defining macros: - -| Symbol | Explanation | -| ---------- | ----------------------------- | -| `print!` | Inline printing. | -| `println!` | Print on a new line. | -| `array!` | Instantiate and fill arrays. | -| `panic!` | Calls `panic` with a message. | - -Contract State Management and Functionality - -# Contract State Management and Functionality - -## Constructors - -Constructors are special functions that execute only once during contract deployment to initialize the contract's state. The example shows a `constructor` that initializes `names` and `total_names` storage variables. - -```cairo,noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# pub trait INameRegistry { -# fn store_name(ref self: TContractState, name: felt252); -# fn get_name(self: @TContractState, address: ContractAddress) -> felt252; -# } -# -# #[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, -# } -# -# #[derive(Drop, Serde, starknet::Store)] -# pub struct Person { -# address: ContractAddress, -# name: felt252, -# } -# - #[constructor] - fn constructor(ref self: ContractState, owner: Person) { - self.names.entry(owner.address).write(owner.name); - self.total_names.write(1); - } -# -# // Public functions inside an impl block -# #[abi(embed_v0)] -# 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_name(self: @ContractState, address: ContractAddress) -> felt252 { -# self.names.entry(address).read() -# } -# } -# -# // Standalone public function -# #[external(v0)] -# fn get_contract_name(self: @ContractState) -> felt252 { -# 'Name Registry' -# } -# -# // 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) { -# let total_names = self.total_names.read(); -# -# self.names.entry(user).write(name); -# -# self.total_names.write(total_names + 1); -# } -# } -# -# // Free function -# fn get_total_names_storage_address(self: @ContractState) -> felt252 { -# self.total_names.__base_address__ -# } -# } -# -# -``` - -## Voting Mechanism - -The `Vote` contract manages voting with constants `YES` (1) and `NO` (0). It registers voters and allows them to cast a vote once. The state is updated to record votes and mark voters as having voted, emitting a `VoteCast` event. Unauthorized attempts, like unregistered users voting or voting again, trigger an `UnauthorizedAttempt` event. - -```cairo,noplayground -/// Core Library Imports for the Traits outside the Starknet Contract -use starknet::ContractAddress; - -/// Trait defining the functions that can be implemented or called by the Starknet Contract -#[starknet::interface] -trait VoteTrait { - /// Returns the current vote status - fn get_vote_status(self: @T) -> (u8, u8, u8, u8); - /// Checks if the user at the specified address is allowed to vote - fn voter_can_vote(self: @T, user_address: ContractAddress) -> bool; - /// Checks if the specified address is registered as a voter - fn is_voter_registered(self: @T, address: ContractAddress) -> bool; - /// Allows a user to vote - fn vote(ref self: T, vote: u8); -} - -/// Starknet Contract allowing three registered voters to vote on a proposal -#[starknet::contract] -mod Vote { - use starknet::storage::{ - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - StoragePointerWriteAccess, - }; - use starknet::{ContractAddress, get_caller_address}; - - const YES: u8 = 1_u8; - const NO: u8 = 0_u8; - - #[storage] - struct Storage { - yes_votes: u8, - no_votes: u8, - can_vote: Map, - registered_voter: Map, - } - - #[constructor] - fn constructor ( - ref self: ContractState, - voter_1: ContractAddress, - voter_2: ContractAddress, - voter_3: ContractAddress, - ) { - self._register_voters(voter_1, voter_2, voter_3); - - self.yes_votes.write(0_u8); - self.no_votes.write(0_u8); - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - VoteCast: VoteCast, - UnauthorizedAttempt: UnauthorizedAttempt, - } - - #[derive(Drop, starknet::Event)] - struct VoteCast { - voter: ContractAddress, - vote: u8, - } - - #[derive(Drop, starknet::Event)] - struct UnauthorizedAttempt { - unauthorized_address: ContractAddress, - } - - #[abi(embed_v0)] - impl VoteImpl of super::VoteTrait { - fn get_vote_status(self: @ContractState) -> (u8, u8, u8, u8) { - let (n_yes, n_no) = self._get_voting_result(); - let (yes_percentage, no_percentage) = self._get_voting_result_in_percentage(); - (n_yes, n_no, yes_percentage, no_percentage) - } - - fn voter_can_vote(self: @ContractState, user_address: ContractAddress) -> bool { - self.can_vote.read(user_address) - } - - fn is_voter_registered(self: @ContractState, address: ContractAddress) -> bool { - self.registered_voter.read(address) - } - - fn vote(ref self: ContractState, vote: u8) { - assert!(vote == NO || vote == YES, "VOTE_0_OR_1"); - let caller: ContractAddress = get_caller_address(); - self._assert_allowed(caller); - self.can_vote.write(caller, false); - - if (vote == NO) { - self.no_votes.write(self.no_votes.read() + 1_u8); - } - if (vote == YES) { - self.yes_votes.write(self.yes_votes.read() + 1_u8); - } - - self.emit(VoteCast { voter: caller, vote: vote }); - } - } - - #[generate_trait] - impl InternalFunctions of InternalFunctionsTrait { - fn _register_voters ( - ref self: ContractState, - voter_1: ContractAddress, - voter_2: ContractAddress, - voter_3: ContractAddress, - ) { - self.registered_voter.write(voter_1, true); - self.can_vote.write(voter_1, true); - - self.registered_voter.write(voter_2, true); - self.can_vote.write(voter_2, true); - - self.registered_voter.write(voter_3, true); - self.can_vote.write(voter_3, true); - } - } - - #[generate_trait] - impl AssertsImpl of AssertsTrait { - fn _assert_allowed(ref self: ContractState, address: ContractAddress) { - let is_voter: bool = self.registered_voter.read((address)); - let can_vote: bool = self.can_vote.read((address)); - - if (!can_vote) { - self.emit(UnauthorizedAttempt { unauthorized_address: address }); - } - - assert!(is_voter, "USER_NOT_REGISTERED"); - assert!(can_vote, "USER_ALREADY_VOTED"); - } - } - - #[generate_trait] - impl VoteResultFunctionsImpl of VoteResultFunctionsTrait { - fn _get_voting_result(self: @ContractState) -> (u8, u8) { - let n_yes: u8 = self.yes_votes.read(); - let n_no: u8 = self.no_votes.read(); - - (n_yes, n_no) - } - - fn _get_voting_result_in_percentage(self: @ContractState) -> (u8, u8) { - let n_yes: u8 = self.yes_votes.read(); - let n_no: u8 = self.no_votes.read(); - - let total_votes: u8 = n_yes + n_no; - - if (total_votes == 0_u8) { - return (0, 0); - } - let yes_percentage: u8 = (n_yes * 100_u8) / (total_votes); - let no_percentage: u8 = (n_no * 100_u8) / (total_votes); - - (yes_percentage, no_percentage) - } - } -} -``` - -## Access Control - -Access control restricts access to contract features based on roles. The pattern involves defining roles (e.g., `owner`, `role_a`) and assigning them to users. Functions can then be restricted to specific roles using guard functions. - -```cairo,noplayground -#[starknet::contract] -mod access_control_contract { - use starknet::storage::{ - Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - StoragePointerWriteAccess, - }; - use starknet::{ContractAddress, get_caller_address}; - - trait IContract { - fn is_owner(self: @TContractState) -> bool; - fn is_role_a(self: @TContractState) -> bool; - fn only_owner(self: @TContractState); - fn only_role_a(self: @TContractState); - fn only_allowed(self: @TContractState); - fn set_role_a(ref self: TContractState, _target: ContractAddress, _active: bool); - fn role_a_action(ref self: ContractState); - fn allowed_action(ref self: ContractState); - } - - #[storage] - struct Storage { - // Role 'owner': only one address - owner: ContractAddress, - // Role 'role_a': a set of addresses - role_a: Map, - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.owner.write(get_caller_address()); - } - - // Guard functions to check roles - - impl Contract of IContract { - #[inline(always)] - fn is_owner(self: @ContractState) -> bool { - self.owner.read() == get_caller_address() - } - - #[inline(always)] - fn is_role_a(self: @ContractState) -> bool { - self.role_a.read(get_caller_address()) - } - - #[inline(always)] - fn only_owner(self: @ContractState) { - assert!(Self::is_owner(self), "Not owner"); - } - - #[inline(always)] - fn only_role_a(self: @ContractState) { - assert!(Self::is_role_a(self), "Not role A"); - } - - // You can easily combine guards to perform complex checks - fn only_allowed(self: @ContractState) { - assert!(Self::is_owner(self) || Contract::is_role_a(self), "Not allowed"); - } - - // Functions to manage roles - - fn set_role_a(ref self: ContractState, _target: ContractAddress, _active: bool) { - Self::only_owner(@self); - self.role_a.write(_target, _active); - } - - // You can now focus on the business logic of your contract - // and reduce the complexity of your code by using guard functions - - fn role_a_action(ref self: ContractState) { - Self::only_role_a(@self); - // ... - } - - fn allowed_action(ref self: ContractState) { - Self::only_allowed(@self); - // ... - } - } -} -``` - -Starknet System Calls - -# Starknet System Calls - -## Call Contract - -This system call invokes a specified function in another contract. - -### Arguments - -- `address`: The address of the contract to call. -- `entry_point_selector`: The selector for the function, computed using `selector!`. -- `calldata`: The array of call arguments. - -### Return Values - -Returns a `SyscallResult>` representing the call response. - -### Notes - -- Internal calls cannot return `Err(_)`. -- Failure of `call_contract_syscall` results in transaction reversion. -- This is a lower-level syntax; use contract interfaces for a more straightforward approach when available. - -## Deploy - -Deploys a new instance of a previously declared contract class. - -### Syntax - -```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; -``` - -### Arguments - -- `class_hash`: The hash of the contract class to deploy. -- `contract_address_salt`: An arbitrary value used in the contract address computation. -- `calldata`: The constructor's calldata. -- `deploy_from_zero`: A flag for contract address computation; uses caller address if false, 0 if true. - -### Return Values - -Returns the contract address and calldata. - -## Get Class Hash At - -Retrieves the class hash of the contract at a specified address. - -### Syntax - -```cairo,noplayground -pub extern fn get_class_hash_at_syscall( - contract_address: ContractAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` - -### Arguments - -- `contract_address`: The address of the deployed contract. - -### Return Values - -Returns the class hash of the contract's originating code. - -## Replace Class - -Replaces the class of the calling contract with a new one. - -### Syntax - -```cairo,noplayground -pub extern fn replace_class_syscall( - class_hash: ClassHash, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` - -### Arguments - -- `class_hash`: The hash of the replacement class. - -### Notes - -- The code executing from the old class will finish. -- The new class is used from the next transaction onwards or subsequent `call_contract` calls. - -## Storage Read - -Reads a value from storage. - -### Syntax - -```cairo,noplayground -pub extern fn storage_read_syscall( - address_domain: u32, address: StorageAddress, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; -``` - -Contract Classes, Addressing, and Deployment - -# Contract Classes, Addressing, and Deployment - -Starknet distinguishes between a contract's definition (class) and its deployed instance. A contract class is the blueprint, while a contract instance is a deployed contract tied to a specific class. - -## Contract Classes - -### Components of a Cairo Class Definition - -A class definition includes: - -- **Contract Class Version**: Currently supported version is 0.1.0. -- **External Functions Entry Points**: Pairs of `(_selector_, _function_idx_)`, where `_selector_` is the `starknet_keccak` hash of the function name and `_function_idx_` is the function's index in the Sierra program. -- **L1 Handlers Entry Points**: Entry points for handling L1 messages. -- **Constructors Entry Points**: Currently, only one constructor is allowed. -- **ABI**: A string representing the contract's ABI. Its hash affects the class hash. The "honest" ABI is the JSON serialization produced by the Cairo compiler. -- **Sierra Program**: An array of field elements representing the Sierra instructions. - -### Class Hash - -Each class is uniquely identified by its class hash, which is the chain hash of its components: - -``` -class_hash = h( - contract_class_version, - external_entry_points, - l1_handler_entry_points, - constructor_entry_points, - abi_hash, - sierra_program_hash -) -``` - -Where `h` is the Poseidon hash function. The hash of an entry point array is `h(selector_1, index_1, ..., selector_n, index_n)`. The `sierra_program_hash` is the Poseidon hash of the program's bytecode array. The `contract_class_version` is the ASCII encoding of `CONTRACT_CLASS_V0.1.0` for domain separation. - -### Working with Classes - -- **Declare**: Use the `DECLARE` transaction to introduce new classes to Starknet's state. -- **Deploy**: Use the `deploy` system call to deploy a new instance of a declared class. -- **Library Call**: Use the `library_call` system call to utilize a declared class's functionality without deploying an instance, similar to Ethereum's `delegatecall`. - -## Contract Instances - -### Contract Nonce - -A contract instance has a nonce, which is the count of transactions originating from that address plus one. The initial nonce for an account deployed via `DEPLOY_ACCOUNT` is `0`. - -### Contract Address - -A contract address is a unique identifier computed as a chain hash of: - -- `prefix`: The ASCII encoding of `STARKNET_CONTRACT_ADDRESS`. -- `deployer_address`: `0` for `DEPLOY_ACCOUNT` transactions, or determined by the `deploy_from_zero` parameter for the `deploy` system call. -- `salt`: Provided by the transaction sender. -- `class_hash`: The hash of the contract's class definition. -- `constructor_calldata_hash`: The hash of the constructor's input arguments. - -The address is computed using the Pedersen hash function: - -``` -contract_address = pedersen( - “STARKNET_CONTRACT_ADDRESS", - deployer_address, - salt, - class_hash, - constructor_calldata_hash) -``` - -## Class Hash Example - -The `ClassHash` type represents the hash of a contract class, enabling multiple contracts to share the same code and facilitating upgrades. - -```cairo -use starknet::ClassHash; - -#[starknet::interface] -pub trait IClassHashExample { - fn get_implementation_hash(self: @TContractState) -> ClassHash; - fn upgrade(ref self: TContractState, new_class_hash: ClassHash); -} - -#[starknet::contract] -mod ClassHashExample { - 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); - } - - #[abi(embed_v0)] - impl ClassHashExampleImpl of super::IClassHashExample { - fn get_implementation_hash(self: @ContractState) -> ClassHash { - self.implementation_hash.read() - } - - fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { - replace_class_syscall(new_class_hash).unwrap(); - self.implementation_hash.write(new_class_hash); - } - } -} -``` - -Advanced Contract Patterns and Examples - -# Advanced Contract Patterns and Examples - -No content available for this section. - -Contract State and Storage - -Introduction to Cairo Contract Storage - -# Introduction to Cairo Contract Storage - -The contract's storage is a persistent storage space where data can be read, written, modified, and persisted. It is structured as a map containing $2^{251}$ slots, with each slot being a `felt252` initialized to 0. - -## Storage Slot Identification - -Each storage slot is uniquely identified by a `felt252` value, referred to as the storage address. This address is computed based on the variable's name and parameters, which are influenced by the variable's type. For further details on the computation of these addresses, refer to the ["Addresses of Storage Variables"][storage addresses] section. - -Accessing and Modifying Contract State - -# Accessing and Modifying Contract State - -Functions can access the contract's state using the `self: ContractState` object, which abstracts underlying system calls like `storage_read_syscall` and `storage_write_syscall`. The compiler uses `ref` and `@` modifiers on `self` to distinguish view and external functions. - -The `#[storage]` attribute is used to annotate the `Storage` struct, enabling interaction with the blockchain state. The `#[abi(embed_v0)]` attribute is required to expose functions defined in an implementation block to the outside world. - -## Accessing and Modifying Storage Variables - -Two primary methods are used to interact with contract state: - -- **`read()`**: This method is called on a storage variable to retrieve its value. It takes no arguments for simple variables. - ```cairo - // Example for a simple variable - self.stored_data.read() - ``` -- **`write(value)`**: This method is used to update the value of a storage variable. It takes the new value as an argument. For complex types like mappings, it may require additional arguments (e.g., key and value). - ```cairo - // Example for a simple variable - self.stored_data.write(x); - ``` - -When `self` is a snapshot of `ContractState` (e.g., in view functions), only read access is permitted, and events cannot be emitted. - -### Accessing Members of Storage Structs - -For storage variables that are structs, you can access and modify individual members directly by calling `read` and `write` on those members. This is more efficient than reading or writing the entire struct at once. - -```cairo -// Reading a member of a struct -self.owner.name.read() - -// Writing to a member of a struct -self.owner.name.write(new_name); -``` - -The `Storage` struct can contain various types, including other structs, enums, and specialized types like Storage Mappings, Vectors, and Nodes. - -### Direct Storage Access (Syscalls) - -While the `ContractState` abstraction is preferred, direct access to storage can be achieved using system calls: - -- **`storage_read_syscall(address_domain: u32, address: StorageAddress)`**: Reads a value from a specified storage address. - - ```cairo - use starknet::storage_access::storage_base_address_from_felt252; - - let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453); - storage_read_syscall(0, storage_address).unwrap_syscall(); - ``` - -- **`storage_write_syscall(address_domain: u32, address: StorageAddress, value: felt252)`**: Writes a value to a specified storage address. - -The `address_domain` parameter is used to separate data availability modes, with domain `0` currently representing the onchain mode. - -You can access the base address of a storage variable using the `__base_address__` attribute. - -Defining Contract State with `#[storage]` - -# Defining Contract State with `#[storage]` - -Contract state in Starknet is managed through a special struct named `Storage`, which must be annotated with the `#[storage]` attribute. This struct defines the variables that will be persisted in the contract's storage. - -```cairo, noplayground -#[storage] -struct Storage { - owner: Person, - expiration: Expiration, -} -``` - -## Storage Variable Types - -Complex types like structs and enums can be used as storage variables, provided they implement the `Drop`, `Serde`, and `starknet::Store` traits. - -### Enums in Storage - -Enums used in contract storage must implement the `Store` trait. This can typically be achieved by deriving it, as long as all associated types also implement `Store`. - -Crucially, enums in storage **must** define a default variant. This default variant is returned when a storage slot is read but has not yet been written to, preventing runtime errors. - -Here's an example of an enum suitable for storage: - -```cairo, noplayground -#[derive(Copy, Drop, Serde, starknet::Store)] -pub enum Expiration { - Finite: u64, - #[default] - Infinite, -} -``` - -Storage Layout and Representation of Custom Types - -# Storage Layout and Representation of Custom Types - -To store custom types in contract storage, they must implement the `starknet::Store` trait. Most core library types already implement this trait. However, memory collections like `Array` and `Felt252Dict` cannot be stored directly; `Vec` and `Map` should be used instead. - -## Storing Custom Types with the `Store` Trait - -For user-defined types like structs or enums, the `Store` trait must be explicitly implemented. This is typically done using the `#[derive(starknet::Store)]` attribute. All members of the struct must also implement the `Store` trait for this derivation to succeed. - -Additionally, custom types often need to derive `Drop` and `Serde` for proper serialization and deserialization of arguments and outputs. - -```cairo, noplayground -#[derive(Drop, Serde, starknet::Store)] -pub struct Person { - address: ContractAddress, - name: felt252, -} - -#[derive(Copy, Drop, Serde, starknet::Store)] -pub enum Expiration { - Finite: u64, - #[default] - Infinite, -} -``` - -## Structs Storage Layout - -Structs are stored in storage as a sequence of primitive types. The elements are laid out in the order they are defined within the struct. The first element resides at the struct's base address, computed as specified in the "Addresses of Storage Variables" section, and subsequent elements are stored at contiguous addresses. - -For a `Person` struct with `address` and `name` fields, the layout is: - -| Fields | Address | -| ------- | ---------------------------- | -| address | `owner.__base_address__` | -| name | `owner.__base_address__ + 1` | - -Tuples are stored similarly, with the first element at the base address and subsequent elements contiguously. - -## Enums Storage Layout - -Enums are stored by their variant's index and any associated values. The index starts at 0 for the first variant and increments for each subsequent variant. If a variant has an associated value, it is stored immediately after the variant's index. - -For the `Expiration` enum: - -**Finite variant:** -| Element | Address | -| ---------------------------- | --------------------------------- | -| Variant index (0 for Finite) | `expiration.__base_address__` | -| Associated limit date | `expiration.__base_address__ + 1` | - -**Infinite variant:** -| Element | Address | -| ------------------------------ | ----------------------------- | -| Variant index (1 for Infinite) | `expiration.__base_address__` | - -The `#[default]` attribute on a variant (e.g., `Infinite`) specifies the value returned when reading an uninitialized enum from storage. - -## Storage Nodes - -Storage nodes are special structs that can contain storage-specific types like `Map`, `Vec`, or other storage nodes. They can only exist within contract storage and are used for creating complex storage layouts, grouping related data, and improving code organization. - -Storage nodes are defined with the `#[starknet::storage_node]` attribute. - -```cairo, noplayground -#[starknet::storage_node] -struct ProposalNode { - title: felt252, - description: felt252, - yes_votes: u32, - no_votes: u32, - voters: Map, -} -``` - -## Modeling of the Contract Storage in the Core Library - -The core library models contract storage using `StoragePointers` and `StoragePaths` to manage storage variable retrieval. Each storage variable can be represented as a `StoragePointer`, which includes: - -- The base address of the variable in storage. -- An offset relative to the base address for the specific storage slot. - -This system facilitates address calculations within the contract's storage space, especially for nested or complex types. - -Working with Storage Nodes, Maps, and Vectors - -# Working with Storage Nodes, Maps, and Vectors - -You can access storage variables using automatically generated `read` and `write` functions. For structs, individual members can be accessed directly. Custom types like structs and enums must implement the `Store` trait, which can be done using `#[derive(starknet::Store)]` or a manual implementation. - -## Addresses of Storage Variables - -The address of a storage variable is computed using `sn_keccak` hash of its name. For complex types, the storage layout is determined by the type's structure. - -- **Single Values**: Address is `sn_keccak` hash of the variable's name. -- **Composed Values (tuples, structs, enums)**: Base address is `sn_keccak` hash of the variable's name. Storage layout depends on the type's structure. -- **Storage Nodes**: Address is a chain of hashes reflecting the node's structure. For a member `m` in `variable_name`, the path is `h(sn_keccak(variable_name), sn_keccak(m))`, where `h` is the Pedersen hash. -- **Maps and Vecs**: Address is computed relative to the storage base address (`sn_keccak` hash of the variable's name) and the keys/indices. - -Structs are stored as sequences of primitive types, while enums store the variant index and associated values. Storage nodes are special structs containing storage-specific types like `Map` or `Vec`, and can only exist within contract storage. - -## Storing Key-Value Pairs with Mappings - -Storage mappings associate keys with values in contract storage. They do not store key data directly; instead, the hash of the key computes the storage slot address for the value. This means iteration over keys is not possible. - -## Working with Vectors - -To retrieve an element from a `Vec`, use the `at` or `get` methods to obtain a storage pointer to the element at a specific index, then call `read`. `at` panics if the index is out of bounds, while `get` returns `None`. - -```cairo -# use starknet::ContractAddress; -# -# #[starknet::interface] -# trait IAddressList { -# fn register_caller(ref self: TState); -# fn get_n_th_registered_address(self: @TState, index: u64) -> Option; -# fn get_all_addresses(self: @TState) -> Array; -# fn modify_nth_address(ref self: TState, index: u64, new_address: ContractAddress); -# } -# -# #[starknet::contract] -# mod AddressList { -# use starknet::storage::{ -# MutableVecTrait, StoragePointerReadAccess, StoragePointerWriteAccess, Vec, VecTrait, -# }; -# use starknet::{ContractAddress, get_caller_address}; -# -# #[storage] -# struct Storage { -# addresses: Vec, -# } -# -# impl AddressListImpl of super::IAddressList { -# fn register_caller(ref self: ContractState) { -# let caller = get_caller_address(); -# self.addresses.push(caller); -# } -# -# fn get_n_th_registered_address( -# self: @ContractState, index: u64, -# ) -> Option { -# if let Some(storage_ptr) = self.addresses.get(index) { -# return Some(storage_ptr.read()); -# } -# return None; -# } -# -# fn get_all_addresses(self: @ContractState) -> Array { -# let mut addresses = array![]; -# for i in 0..self.addresses.len() { -# addresses.append(self.addresses.at(i).read()); -# } -# addresses -# } -# - fn modify_nth_address(ref self: ContractState, index: u64, new_address: ContractAddress) { - let mut storage_ptr = self.addresses.at(index); - storage_ptr.write(new_address); - } -# } -# } -# -# -``` - -To retrieve all elements of a `Vec`, iterate through its indices, read each value, and append it to a memory `Array`. Conversely, you cannot store a memory `Array` directly in storage; you must iterate over its elements and append them to a storage `Vec`. - -To modify the address stored at a specific index of a `Vec`: - -```cairo -# use starknet::ContractAddress; -# -# #[starknet::interface] -# trait IAddressList { -# fn register_caller(ref self: TState); -# fn get_n_th_registered_address(self: @TState, index: u64) -> Option; -# fn get_all_addresses(self: @TState) -> Array; -# fn modify_nth_address(ref self: TState, index: u64, new_address: ContractAddress); -# } -# -# #[starknet::contract] -# mod AddressList { -# use starknet::storage::{ -# MutableVecTrait, StoragePointerReadAccess, StoragePointerWriteAccess, Vec, VecTrait, -# }; -# use starknet::{ContractAddress, get_caller_address}; -# -# #[storage] -# struct Storage { -# addresses: Vec, -# } -# -# impl AddressListImpl of super::IAddressList { -# fn register_caller(ref self: ContractState) { -# let caller = get_caller_address(); -# self.addresses.push(caller); -# } -# -# fn get_n_th_registered_address( -# self: @ContractState, index: u64, -# ) -> Option { -# if let Some(storage_ptr) = self.addresses.get(index) { -# return Some(storage_ptr.read()); -# } -# return None; -# } -# -# fn get_all_addresses(self: @ContractState) -> Array { -# let mut addresses = array![]; -# for i in 0..self.addresses.len() { -# addresses.append(self.addresses.at(i).read()); -# } -# addresses -# } -# - fn modify_nth_address(ref self: ContractState, index: u64, new_address: ContractAddress) { - let mut storage_ptr = self.addresses.at(index); - storage_ptr.write(new_address); - } -# } -# } -# -# -``` - -Advanced Storage Concepts and System Calls - -# Advanced Storage Concepts and System Calls - -There is no content available for this section. - -Examples and Best Practices - -# Examples and Best Practices - -Storage Mappings and Vectors - -Storage Mappings - -# Storage Mappings - -Mappings in Cairo, declared using the `Map` type from `core::starknet::storage`, are used for persistent key-value storage in contracts. Unlike memory dictionaries like `Felt252Dict`, `Map` is a phantom type specifically designed for contract storage and has limitations: it cannot be instantiated as a regular variable, used as a function parameter, or included in regular structs. It can only be declared as a storage variable within a contract's storage struct. - -Mappings do not inherently track length or whether a key-value pair exists; all unassigned values default to `0`. To remove an entry, set its value to the type's default. - -## Declaring and Using Storage Mappings - -To declare a mapping, specify the key and value types within angle brackets, e.g., `Map`. - -```cairo -#[starknet::contract] -mod UserValues { - use starknet::storage::Map; - use starknet::{ContractAddress, get_caller_address}; - - #[storage] - struct Storage { - user_values: Map, - } - - impl UserValuesImpl of super::IUserValues { - fn set(ref self: ContractState, amount: u64) { - let caller = get_caller_address(); - self.user_values.entry(caller).write(amount); - } - - fn get(self: @ContractState, address: ContractAddress) -> u64 { - self.user_values.entry(address).read() - } - } -} -``` - -To read a value, obtain the storage pointer for the key using `.entry(key)` and then call `.read()`: - -```cairo -fn get(self: @ContractState, address: ContractAddress) -> u64 { - self.user_values.entry(address).read() -} -``` - -To write a value, obtain the storage pointer for the key using `.entry(key)` and then call `.write(value)`: - -```cairo -fn set(ref self: ContractState, amount: u64) { - let caller = get_caller_address(); - self.user_values.entry(caller).write(amount); -} -``` - -## Nested Mappings - -Mappings can be nested to create more complex data structures. For example, a mapping can store multiple items and their quantities for each user: - -```cairo -#[starknet::contract] -mod WarehouseContract { - use starknet::storage::Map; - use starknet::{ContractAddress, get_caller_address}; - - #[storage] - struct Storage { - user_warehouse: Map>, - } - - 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() - } - } -} -``` - -Accessing nested mappings involves chaining `.entry()` calls, for example: `self.user_warehouse.entry(caller).entry(item_id).write(quantity)`. - -Storage Vectors - -# Storage Vectors - -The `Vec` type from the `core::starknet::storage` module allows storing collections of values in contract storage. To use it, you need to import `VecTrait` and `MutableVecTrait` for read and write operations. - -It's important to note that `Array` is a memory type and cannot be directly stored in contract storage. `Vec` is a phantom type for storage but has limitations: it cannot be instantiated as a regular variable, used as a function parameter, or included as a member in regular structs. To work with its full contents, elements must be copied to and from a memory `Array`. - -## Declaring and Using Storage Vectors - -To declare a storage vector, use `Vec` with the element type in angle brackets. The `push` method adds an element to the end of the vector. - -```cairo, noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# trait IAddressList { -# fn register_caller(ref self: TState); -# fn get_n_th_registered_address(self: @TState, index: u64) -> Option; -# fn get_all_addresses(self: @TState) -> Array; -# fn modify_nth_address(ref self: TState, index: u64, new_address: ContractAddress); -# } -# -#[starknet::contract] -mod AddressList { - use starknet::storage::{ - MutableVecTrait, StoragePointerReadAccess, StoragePointerWriteAccess, Vec, VecTrait, - }; - use starknet::{ContractAddress, get_caller_address}; - - #[storage] - struct Storage { - addresses: Vec, - } - - impl AddressListImpl of super::IAddressList { - fn register_caller(ref self: ContractState) { - let caller = get_caller_address(); - self.addresses.push(caller); - } - - fn get_n_th_registered_address( - self: @ContractState, index: u64, - ) -> Option { - if let Some(storage_ptr) = self.addresses.get(index) { - return Some(storage_ptr.read()); - } - return None; - } - - fn get_all_addresses(self: @ContractState) -> Array { - let mut addresses = array![]; - for i in 0..self.addresses.len() { - addresses.append(self.addresses.at(i).read()); - } - addresses - } - - fn modify_nth_address(ref self: ContractState, index: u64, new_address: ContractAddress) { - let mut storage_ptr = self.addresses.at(index); - storage_ptr.write(new_address); - } - } -} -``` - -Listing 15-3: Declaring a storage `Vec` in the Storage struct - -Elements can be accessed by index using `get(index)` which returns a `StoragePointerReadAccess`, and modified using `at(index)` which returns a `StoragePointerWriteAccess`. - -Starknet Address Types - -# Starknet Address Types - -Starknet provides specialized types for interacting with the blockchain, including addresses for contracts, storage, and Ethereum compatibility. - -## Contract Address - -The `ContractAddress` type represents the unique identifier of a deployed contract on Starknet. It is used for calling other contracts, verifying caller identities, and managing access control. - -```cairo -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, - } - - #[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() - } - - 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); - } - } -} -``` - -Contract addresses in Starknet have a value range of `[0, 2^251)`, which is 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 typically managed by storage systems (like `Map` and `Vec`), understanding it is crucial for advanced storage patterns. Each value in the `Storage` struct has a `StorageAddress` that can be accessed directly. - -```cairo -#[starknet::contract] -mod StorageExample { - use starknet::storage_access::StorageAddress; - - #[storage] - struct Storage { - value: u256, - } - - // 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() - } -} -``` - -Storage addresses share the same value range as contract addresses `[0, 2^251)`. The related `StorageBaseAddress` type has a slightly smaller range `[0, 2^251 - 256)` to accommodate offset calculations. - -## Ethereum Address - -The `EthAddress` type represents a 20-byte Ethereum address, primarily used for cross-chain applications on Starknet, such as L1-L2 messaging and token bridges. - -```cairo -use starknet::EthAddress; - -#[starknet::interface] -pub trait IEthAddressExample { - fn set_l1_contract(ref self: TContractState, l1_contract: EthAddress); - fn send_message_to_l1(ref self: TContractState, recipient: EthAddress, amount: felt252); -} - -#[starknet::contract] -mod EthAddressExample { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::syscalls::send_message_to_l1_syscall; - use super::EthAddress; - - #[storage] - struct Storage { - l1_contract: EthAddress, - } - - #[abi(embed_v0)] - impl EthAddressExampleImpl of super::IEthAddressExample { - fn set_l1_contract(ref self: ContractState, l1_contract: EthAddress) { - self.l1_contract.write(l1_contract); - } - - 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(); - } - } - - #[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... - } -} -``` - -Contract Functions and Entrypoints - -Public, External, and View Functions - -# Public, External, and View Functions - -In Starknet, functions are categorized based on their accessibility and state-mutating capabilities: - -- **Public Function:** Exposed to the outside world, callable from both external transactions and within the contract itself. In the example, `set` and `get` are public functions. -- **External Function:** A public function that can be invoked via a Starknet transaction and can mutate the contract's state. `set` is an example of an external function. -- **View Function:** A public function that is generally read-only and cannot mutate the contract's state. This restriction is enforced by the compiler. - -```cairo,noplayground - #[abi(embed_v0)] - impl SimpleStorage of super::ISimpleStorage { - fn set(ref self: ContractState, x: u128) { - self.stored_data.write(x); - } - - fn get(self: @ContractState) -> u128 { - self.stored_data.read() - } - } -``` - -To ensure the contract implementation aligns with its interface (defined as a trait, e.g., `ISimpleStorage`), public functions must be defined within an implementation of that trait. - -State Mutability and Function Behavior - -# State Mutability and Function Behavior - -## View Functions - -View functions are public functions that accept `self: ContractState` as a snapshot, allowing only read access to storage variables. They restrict writes to storage through `self`, leading to compilation errors if attempted. The compiler marks their state mutability as `view`, preventing any state modification via `self`. - -```cairo,noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# pub trait INameRegistry { -# fn store_name(ref self: TContractState, name: felt252); -# fn get_name(self: @TContractState, address: ContractAddress) -> felt252; -# } -# -# #[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, -# } -# -# #[derive(Drop, Serde, starknet::Store)] -# pub struct Person { -# address: ContractAddress, -# name: felt252, -# } -# -# #[constructor] -# fn constructor(ref self: ContractState, owner: Person) { -# self.names.entry(owner.address).write(owner.name); -# self.total_names.write(1); -# } -# -# // Public functions inside an impl block -# #[abi(embed_v0)] -# 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_name(self: @ContractState, address: ContractAddress) -> felt252 { - self.names.entry(address).read() - } -# } -# -# // Standalone public function -# #[external(v0)] -# fn get_contract_name(self: @ContractState) -> felt252 { -# 'Name Registry' -# } -# -# // 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) { -# let total_names = self.total_names.read(); -# -# self.names.entry(user).write(name); -# -# self.total_names.write(total_names + 1); -# } -# } -# -# // Free function -# fn get_total_names_storage_address(self: @ContractState) -> felt252 { -# self.total_names.__base_address__ -# } -# } -# -# -``` - -Defining Entrypoints with Cairo Attributes - -# Defining Entrypoints with Cairo Attributes - -Standalone public functions can be defined outside of an `impl` block using the `#[external(v0)]` attribute. This automatically generates an entry in the contract ABI, making these functions callable from outside the contract. The first parameter of such functions must be `self`. - -The `#[generate_trait]` attribute automatically generates a trait definition for an implementation block, simplifying the process of defining functions without explicit trait definitions. It is often used for private `impl` blocks. - -The `#[abi(per_item)]` attribute, when applied to an `impl` block (often in conjunction with `#[generate_trait]`), allows for individual function entrypoint definitions. Functions within such an `impl` block must be annotated with `#[external(v0)]` to be exposed as public entrypoints; otherwise, they are treated as private. - -```cairo -// Example of a standalone public function -#[external(v0)] -fn get_contract_name(self: @ContractState) -> felt252 { - 'Name Registry' -} - -// Example using #[abi(per_item)] and #[external(v0)] -#[starknet::contract] -mod ContractExample { - #[storage] - struct Storage {} - - #[abi(per_item)] - #[generate_trait] - impl SomeImpl of SomeTrait { - #[constructor] - fn constructor(ref self: ContractState) {} - - #[external(v0)] - fn external_function(ref self: ContractState, arg1: felt252) {} - - #[l1_handler] - fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {} - - fn internal_function(self: @ContractState) {} - } -} -``` - -Entrypoint Types and Contract Interaction Patterns - -# Entrypoint Types and Contract Interaction Patterns - -Contracts interact with each other in Cairo using the **dispatcher** pattern. This involves a specific type that implements methods to call functions of another contract, automatically handling data encoding and decoding. The JSON ABI is crucial for correctly encoding and decoding data when interacting with smart contracts, as seen in block explorers. - -## Entrypoints - -Entrypoints are functions exposed in a contract's ABI that can be called from outside the contract. There are three types of entrypoints in Starknet contracts: - -- **Public Functions**: These are the most common entrypoints. They are exposed as either `view` (read-only) or `external` (state-mutating), depending on their state mutability. Note that a `view` function might still modify the contract's state if it uses low-level calls not enforced for immutability by the compiler. -- **Constructor**: An optional, unique entrypoint called only once during contract deployment. -- **L1-Handlers**: Functions that can only be triggered by the sequencer after receiving a message from the L1 network, which includes an instruction to call a contract. - -A function entrypoint is represented by a selector and a `function_idx` within a Cairo contract class. - -Events in Starknet Contracts - -Defining Events in Starknet Contracts - -# Defining Events in Starknet Contracts - -Events are custom data structures emitted by a smart contract during execution, stored in transaction receipts for external tools to parse and index. - -Events are defined within an enum annotated with `#[event]`, which must be named `Event`. Each variant of this enum represents a distinct event that can be emitted by the contract, with its associated data being any struct or enum that implements the `starknet::Event` trait, achieved by adding `#[derive(starknet::Event)]`. - -```cairo -#[event] -#[derive(Drop, starknet::Event)] -pub enum Event { - BookAdded: BookAdded, - #[flat] - FieldUpdated: FieldUpdated, - BookRemoved: BookRemoved, -} - -#[derive(Drop, starknet::Event)] -pub struct BookAdded { - pub id: u32, - pub title: felt252, - #[key] - pub author: felt252, -} - -#[derive(Drop, starknet::Event)] -pub enum FieldUpdated { - Title: UpdatedTitleData, - Author: UpdatedAuthorData, -} - -#[derive(Drop, starknet::Event)] -pub struct UpdatedTitleData { - #[key] - pub id: u32, - pub new_title: felt252, -} - -#[derive(Drop, starknet::Event)] -pub struct UpdatedAuthorData { - #[key] - pub id: u32, - pub new_author: felt252, -} - -#[derive(Drop, starknet::Event)] -pub struct BookRemoved { - pub id: u32, -} -``` - -The `#[key]` attribute can be applied to event data fields. These "key" fields are stored separately from data fields, enabling external tools to filter events based on these keys. - -Emitting Events and Contract Interactions - -# Emitting Events and Contract Interactions - -Starknet contracts can emit events to signal state changes or important occurrences. These events can be filtered and retrieved using methods like `starknet_getEvents`. - -## Emitting Events using `emit_event_syscall` - -The `emit_event_syscall` is a low-level function for emitting events. - -### Syntax - -```cairo -pub extern fn emit_event_syscall( - keys: Span, data: Span, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` - -### Description - -This function emits an event with a specified set of keys and data. The `keys` argument is analogous to Ethereum's event topics, allowing for event filtering. The `data` argument contains the event's payload. - -### Example - -The following example demonstrates emitting an event with two keys and three data elements: - -```cairo -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(); -``` - -## Emitting Events using `self.emit()` - -A more convenient way to emit events is by using the `self.emit()` method, which takes an event data structure as a parameter. - -### Defining Events - -Events are defined using `struct` or `enum` types, annotated with `#[starknet::Event]`. - -- **Keys:** Fields marked with `#[key]` are considered event keys, used for filtering. -- **Data:** Fields not marked with `#[key]` are part of the event data. -- **Variant Names:** For enums, the variant name is used as the first event key to represent the event's name. - -### The `#[flat]` Attribute - -The `#[flat]` attribute can be applied to enum variants to flatten the event structure. When used, the inner variant's name becomes the event name instead of the outer enum variant's name. This is useful for nested event structures. - -### Example of Emitting Events - -This example defines three events: `BookAdded`, `FieldUpdated`, and `BookRemoved`. `BookAdded` and `BookRemoved` use simple structs, while `FieldUpdated` uses an enum of structs. The `author` field in `BookAdded` is marked as a key. The `FieldUpdated` enum variant is marked with `#[flat]`. - -```cairo -#[starknet::interface] -pub trait IEventExample { - fn add_book(ref self: TContractState, id: u32, title: felt252, author: felt252); - fn change_book_title(ref self: TContractState, id: u32, new_title: felt252); - fn change_book_author(ref self: TContractState, id: u32, new_author: felt252); - fn remove_book(ref self: TContractState, id: u32); -} - -#[starknet::contract] -mod EventExample { - #[storage] - struct Storage {} - - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - BookAdded: BookAdded, - #[flat] - FieldUpdated: FieldUpdated, - BookRemoved: BookRemoved, - } - - #[derive(Drop, starknet::Event)] - pub struct BookAdded { - pub id: u32, - pub title: felt252, - #[key] - pub author: felt252, - } - - #[derive(Drop, starknet::Event)] - pub enum FieldUpdated { - Title: UpdatedTitleData, - Author: UpdatedAuthorData, - } - - #[derive(Drop, starknet::Event)] - pub struct UpdatedTitleData { - #[key] - pub id: u32, - pub new_title: felt252, - } - - #[derive(Drop, starknet::Event)] - pub struct UpdatedAuthorData { - #[key] - pub id: u32, - pub new_author: felt252, - } - - #[derive(Drop, starknet::Event)] - pub struct BookRemoved { - pub id: u32, - } - - #[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 }); - } - - fn change_book_title(ref self: ContractState, id: u32, new_title: felt252) { - self.emit(FieldUpdated::Title(UpdatedTitleData { id, new_title })); - } - - fn change_book_author(ref self: ContractState, id: u32, new_author: felt252) { - self.emit(FieldUpdated::Author(UpdatedAuthorData { id, new_author })); - } - - fn remove_book(ref self: ContractState, id: u32) { - self.emit(BookRemoved { id }); - } - } -} -``` - -Event Data and Transaction Receipts - -# Event Data and Transaction Receipts - -To understand how events are stored in transaction receipts, let's examine two examples: - -### Example 1: Add a book - -When invoking the `add_book` function with `id` = 42, `title` = 'Misery', and `author` = 'S. King', the transaction receipt's "events" section will contain: - -```json -"events": [ - { - "from_address": "0x27d07155a12554d4fd785d0b6d80c03e433313df03bb57939ec8fb0652dbe79", - "keys": [ - "0x2d00090ebd741d3a4883f2218bd731a3aaa913083e84fcf363af3db06f235bc", - "0x532e204b696e67" - ], - "data": [ - "0x2a", - "0x4d6973657279" - ] - } - ] -``` - -In this receipt: - -- `from_address`: The address of the smart contract. -- `keys`: Contains serialized key fields of the emitted event. - - The first key is the selector of the event name (`selector!("BookAdded")`). - - The second key (`0x532e204b696e67 = 'S. King'`) is the `author` field, marked with `#[key]`. -- `data`: Contains serialized data fields of the event. - - The first item (`0x2a = 42`) is the `id` field. - - The second item (`0x4d6973657279 = 'Misery'`) is the `title` field. - -### Example 2: Update a book author - -When invoking `change_book_author` with `id` = 42 and `new_author` = 'Stephen King', which emits a `FieldUpdated` event, the transaction receipt's "events" section will show: - -```json -"events": [ - { - "from_address": "0x27d07155a12554d4fd785d0b6d80c03e433313df03bb57939ec8fb0652dbe79", - "keys": [ - "0x1b90a4a3fc9e1658a4afcd28ad839182217a69668000c6104560d6db882b0e1", - "0x2a" - ], - "data": [ - "0x5374657068656e204b696e67" - ] - } - ] -``` - -In this receipt for the `FieldUpdated` event: - -- `from_address`: The address of the smart contract. -- `keys`: Contains serialized key fields. - - The first key is the selector for `FieldUpdated`. - - The second key (`0x2a`) is the `id` of the book being updated. -- `data`: Contains serialized data fields. - - The `data` field (`0x5374657068656e204b696e67 = 'Stephen King'`) is the new author's name. - -Interacting with Starknet Contracts - -Introduction to Starknet Contract Interaction - -# Introduction to Starknet Contract Interaction - -Mechanisms for Interacting with Starknet Contracts - -# Mechanisms for Interacting with Starknet Contracts - -A smart contract requires an external trigger to execute. Interaction between contracts enables complex applications. This chapter covers interacting with smart contracts, the Application Binary Interface (ABI), calling contracts, and contract communication. It also details using classes as libraries. - -## Contract Class ABI - -The Contract Class Application Binary Interface (ABI) is the high-level specification of a contract's interface. It details callable functions, their parameters, and return values, enabling external communication through data encoding and decoding. A JSON representation, generated from the contract class, is typically used by external sources. - -## Calling Contracts Using the Contract Dispatcher - -Contract dispatchers are structs that wrap a contract address and implement a generated trait for the contract's interface. The implementation includes: - -- Serialization of function arguments into a `felt252` array (`__calldata__`). -- A low-level contract call using `contract_call_syscall` with the contract address, function selector, and `__calldata__`. -- Deserialization of the returned value. - -The following example demonstrates a contract that interacts with an ERC20 contract, calling its `name` and `transfer_from` functions: - -```cairo,noplayground -# use starknet::ContractAddress; -# -# #[starknet::interface] -# trait IERC20 { -# fn name(self: @TContractState) -> felt252; -# -# 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] - struct Storage {} - - impl TokenWrapper of ITokenWrapper { - fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { - IERC20Dispatcher { contract_address }.name() - } - - fn transfer_token( - ref self: ContractState, - address: ContractAddress, - recipient: ContractAddress, - amount: u256, - ) -> bool { - let erc20_dispatcher = IERC20Dispatcher { contract_address: address }; - erc20_dispatcher.transfer_from(get_caller_address(), recipient, amount) - } - } -} -# -# -``` - -## Handling Errors with Safe Dispatchers - -Safe dispatchers return a `Result>`, allowing for error handling. However, certain scenarios still cause immediate transaction reverts, including failures in Cairo Zero contract calls, library calls to non-existent classes, calls to non-existent contract addresses, and various `deploy` or `replace_class` syscall failures. - -## Calling Contracts using Low-Level Calls - -The `call_contract_syscall` provides direct control over serialization and deserialization for contract calls. Arguments must be serialized into a `Span`. The call returns serialized values that need manual deserialization. - -The following example demonstrates calling the `transfer_from` function of an ERC20 contract using `call_contract_syscall`: - -```cairo,noplayground -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); - - let mut res = syscalls::call_contract_syscall( - address, selector!("transfer_from"), call_data.span(), - ) - .unwrap_syscall(); - - Serde::::deserialize(ref res).unwrap() - } - } -} -``` - -To use this syscall, provide the contract address, the function selector, and serialized call arguments. - -## Executing Code from Another Class (Library Calls) - -Library calls allow a contract to execute logic from another class within its own context, updating its own state, unlike contract calls which execute in the context of the called contract. - -When contract A calls contract B: - -- The execution context is B's. -- `get_caller_address()` in B returns A's address. -- `get_contract_address()` in B returns B's address. -- Storage updates in B affect B's storage. - -Using Starkli for Contract Interaction - -# Using Starkli for Contract Interaction - -This section outlines how to deploy a contract and interact with its functions using the `starkli` command-line tool. - -## Deploying a Contract - -The following command deploys a voting contract and registers specified voter addresses as eligible. The constructor arguments are the addresses of the voters. - -```bash -starkli deploy --rpc http://0.0.0.0:5050 --account katana-0 -``` - -An example deployment command: - -```bash -starkli deploy 0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 0x033c627a3e5213790e246a917770ce23d7e562baa5b4d2917c23b1be6d91961c 0x01d98d835e43b032254ffbef0f150c5606fa9c5c9310b1fae370ab956a7919f5 --rpc http://0.0.0.0:5050 --account katana-0 -``` - -After deployment, the contract will be available at a specific address (e.g., `0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349`), which will differ for each deployment. - -## Voter Eligibility Verification - -The voting contract includes functions to verify voter eligibility: `voter_can_vote` and `is_voter_registered`. These are external read functions, meaning they do not modify the contract's state. - -- `is_voter_registered`: Checks if a given address is registered as an eligible voter. -- `voter_can_vote`: Checks if a voter at a specific address is eligible to vote (i.e., registered and has not yet voted). - -These functions can be called using the `starkli call` command. The `call` command is for read-only functions and does not require signing, unlike the `invoke` command which is used for state-modifying functions. - -```bash+ -starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 voter_can_vote 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 --rpc http://0.0.0.0:5050 -``` - -Practical Application: Interacting with Oracles and ERC20 Contracts - -# Practical Application: Interacting with Oracles and ERC20 Contracts - -The Pragma oracle provides price feeds for various token pairs. When consuming these feeds, it's important to note that Pragma returns values with a decimal factor of 6 or 8. To convert these values to a required decimal factor, divide by $10^n$, where $n$ is the decimal factor. - -An example contract interacting with the Pragma oracle and an ERC20 contract (like ETH) is provided. This contract imports `IPragmaABIDispatcher` and `ERC20ABIDispatcher`. It defines a constant for the `ETH/USD` token pair and storage for the Pragma contract address and the product price in USD. The `buy_item` function retrieves the ETH price from the oracle, calculates the required ETH amount, checks the caller's ETH balance using `balance_of` from the ERC20 contract, and then transfers the ETH using `transfer_from`. - -## Interacting with Starknet Contracts using `starkli` - -The `starkli` tool can be used to interact with Starknet contracts. The general syntax involves specifying the contract address, the function to call, and the function's input. - -### Checking Voter Eligibility - -To check if a voter is eligible, you can use the `call` command with the `is_voter_registered` function. - -```bash -starkli call 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 is_voter_registered 0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0 --rpc http://0.0.0.0:5050 -``` - -This command returns `1` (true) if the voter is registered. Calling with an unregistered address returns `0` (false). - -### Casting a Vote - -To cast a vote, use the `invoke` command with the `vote` function, providing `1` for Yes or `0` for No as input. This operation requires a fee and the transaction must be signed. - -```bash -# Voting Yes -starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 1 --rpc http://0.0.0.0:5050 --account katana-0 - -# Voting No -starkli invoke 0x05ea3a690be71c7fcd83945517f82e8861a97d42fca8ec9a2c46831d11f33349 vote 0 --rpc http://0.0.0.0:5050 --account katana-0 -``` - -After invoking, you can check the transaction status using: - -```bash -starkli transaction --rpc http://0.0.0.0:5050 -``` - -### Handling Double Voting Errors - -Attempting to vote twice with the same signer results in a `ContractError` with the reason `USER_ALREADY_VOTED`. This can be confirmed by inspecting the `katana` node's output, which provides more detailed error messages. - -``` -Transaction execution error: "Error in the called contract (0x03ee9e18edc71a6df30ac3aca2e0b02a198fbce19b7480a63a0d71cbd76652e0):\n Error at pc=0:81:\n Got an exception while executing a hint: Custom Hint Error: Execution failed. Failure reason: \"USER_ALREADY_VOTED\".\n ...\n" -``` - -The contract logic includes an assertion: `assert!(can_vote, "USER_ALREADY_VOTED");`. - -To manage multiple signers for voting, create Signer and Account Descriptors for each account, deriving Signers from private keys and Account Descriptors from public keys, smart wallet addresses, and the smart wallet class hash. - -Dispatchers and Library Calls - -Understanding Cairo Dispatchers - -# Understanding Cairo Dispatchers - -Cairo utilizes dispatcher patterns to facilitate interactions between contracts and libraries. These dispatchers abstract the complexity of low-level system calls, providing a type-safe interface for contract interactions. - -## Types of Dispatchers - -There are two primary categories of dispatchers: - -- **Contract Dispatchers**: Such as `IERC20Dispatcher` and `IERC20SafeDispatcher`, these wrap a `ContractAddress` and are used to invoke functions on other deployed contracts. -- **Library Dispatchers**: Such as `IERC20LibraryDispatcher` and `IERC20SafeLibraryDispatcher`, these wrap a class hash and are used to call functions within classes. Their usage will be detailed in a subsequent chapter. - -The `'Safe'` variants of these dispatchers offer the capability to manage and handle potential errors that might arise during the execution of a call. - -Under the hood, dispatchers leverage the `contract_call_syscall`. This system call allows for the invocation of functions on other contracts by providing the contract address, the function selector, and the necessary arguments. The dispatcher pattern simplifies this process, abstracting the syscall's intricacies. - -## The Dispatcher Pattern Example (`IERC20`) - -The compiler automatically generates dispatcher structs and traits for given interfaces. Below is an example for an `IERC20` interface exposing a `name` view function and a `transfer` external function: - -```cairo,noplayground -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__); - - 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__, - ); - () - } -} -``` - -Interacting with Other Contracts via Dispatchers - -# Interacting with Other Contracts via Dispatchers - -The dispatcher pattern provides a structured way to call functions on other contracts. It involves using a struct that wraps the target contract's address and implements a dispatcher trait, which is automatically generated from the contract's ABI. This approach leverages Cairo's trait system for type-safe interactions. - -## Dispatcher Pattern - -Functions in Cairo are identified by selectors, which are derived from function names using `sn_keccak(function_name)`. Dispatchers abstract the process of computing these selectors and making low-level system calls or RPC interactions. - -When a contract interface is defined, the Cairo compiler automatically generates and exports dispatchers. For example, an `IERC20` interface would generate an `IERC20Dispatcher` struct and an `IERC20DispatcherTrait`. - -```cairo -// Example usage of a dispatcher -#[starknet::interface] -pub trait IERC20 { - fn name(self: @TState) -> felt252; - fn transfer(ref self: TState, recipient: felt252, amount: u256); -} - -// ... inside a contract ... -// let contract_address = 0x123.try_into().unwrap(); -// let erc20_dispatcher = IERC20Dispatcher { contract_address }; -// let name = erc20_dispatcher.name(); -// erc20_dispatcher.transfer(recipient_address, amount); -``` - -## Handling Errors with Safe Dispatchers - -'Safe' dispatchers, such as `IERC20SafeDispatcher`, enable calling contracts to gracefully handle potential execution errors. If a function called via a safe dispatcher panics, the execution returns to the caller, and the safe dispatcher returns a `Result::Err` containing the panic reason. - -Consider the following example using a hypothetical `IFailableContract` interface: - -```cairo,noplayground -#[starknet::interface] -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 - }, - } -} -``` - -Leveraging Library Calls for Logic Execution - -# Leveraging Library Calls for Logic Execution - -When a contract (A) uses a library call to invoke the logic of another class (B), the execution context remains that of contract A. This means that functions like `get_caller_address()` and `get_contract_address()` within B's logic will return values pertaining to A's context. Similarly, any storage updates made within B's class will affect A's storage. - -Library calls can be implemented using a dispatcher pattern, similar to contract dispatchers but utilizing a class hash instead of a contract address. The primary difference in the underlying mechanism is the use of `library_call_syscall` instead of `call_contract_syscall`. - -## Library Dispatcher Example - -Listing 16-5 demonstrates a simplified `IERC20LibraryDispatcher` and its associated trait and implementation: - -```cairo -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 IERC20LibraryDispatcher { - class_hash: starknet::ClassHash, -} - -impl IERC20LibraryDispatcherImpl of IERC20DispatcherTrait< IERC20LibraryDispatcher> { - 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 - } -} -``` - -This example illustrates how to set up a dispatcher for library calls, enabling the execution of logic from another class within the current contract's context. - -Practical Examples of Dispatchers and Library Calls - -# Practical Examples of Dispatchers and Library Calls - -This section demonstrates practical examples of using dispatchers and low-level calls for interacting with Cairo contracts. - -## Using Library Dispatchers - -This example defines two contracts: `ValueStoreLogic` for the core logic and `ValueStoreExecutor` to execute that logic. `ValueStoreExecutor` imports and uses `IValueStoreLibraryDispatcher` to make library calls to `ValueStoreLogic`. - -```cairo,noplayground -#[starknet::interface] -trait IValueStore { - fn set_value(ref self: TContractState, value: u128); - fn get_value(self: @TContractState) -> u128; -} - -#[starknet::contract] -mod ValueStoreLogic { - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - #[storage] - struct Storage { - value: u128, - } - - #[abi(embed_v0)] - impl ValueStore of super::IValueStore { - fn set_value(ref self: ContractState, value: u128) { - self.value.write(value); - } - - fn get_value(self: @ContractState) -> u128 { - self.value.read() - } - } -} - -#[starknet::contract] -mod ValueStoreExecutor { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, ContractAddress}; - use super::{IValueStoreDispatcherTrait, IValueStoreLibraryDispatcher}; - - #[storage] - struct Storage { - logic_library: ClassHash, - value: u128, - } - - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); - } - - #[abi(embed_v0)] - impl ValueStoreExecutor of super::IValueStore { - fn set_value(ref self: ContractState, value: u128) { - IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() } - .set_value((value)); - } - - 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() - } -} -``` - -When `set_value` is called on `ValueStoreExecutor`, it performs a library call to `ValueStoreLogic.set_value`, updating `ValueStoreExecutor`'s storage. Similarly, `get_value` calls `ValueStoreLogic.get_value`, retrieving the value from `ValueStoreExecutor`'s context. Consequently, both `get_value` and `get_value_local` return the same value as they access the same storage slot. - -## Calling Classes using Low-Level Calls (`library_call_syscall`) - -An alternative to dispatchers is using the `library_call_syscall` directly, which offers more control over serialization, deserialization, and error handling. - -The following example demonstrates calling the `set_value` function of a `ValueStore` contract using `library_call_syscall`: - -```cairo,noplayground -#[starknet::contract] -mod ValueStore { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ClassHash, SyscallResultTrait, syscalls}; - - #[storage] - struct Storage { - logic_library: ClassHash, - value: u128, - } - - #[constructor] - fn constructor(ref self: ContractState, logic_library: ClassHash) { - self.logic_library.write(logic_library); - } - - #[external(v0)] - fn set_value(ref self: ContractState, value: u128) -> bool { - let mut call_data: Array = array![]; - Serde::serialize(@value, ref call_data); - - let mut res = syscalls::library_call_syscall( - self.logic_library.read(), selector!("set_value"), call_data.span(), - ) - .unwrap_syscall(); - - Serde::::deserialize(ref res).unwrap() - } - - #[external(v0)] - fn get_value(self: @ContractState) -> u128 { - self.value.read() - } -} -``` - -Data Serialization for Starknet - -Cairo Data Serialization Fundamentals - -# Cairo Data Serialization Fundamentals - -Serialization is the process of converting data structures into a format that can be easily stored or transmitted. In Cairo, this is primarily handled by the `Serde` trait. - -## Serialization and Deserialization with `Serde` - -The `Serde` trait allows for the conversion of Cairo data structures into an array of `felt252` (serialization) and back from an array of `felt252` (deserialization). - -For a struct to be serialized, it must derive both `Serde` and `Drop`. - -**Serialization Example:** - -```cairo -#[derive(Serde, Drop)] -struct A { - item_one: felt252, - item_two: felt252, -} - -#[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); -} -``` - -Running this example outputs `[2, 99 ('c'), ]`, showing the struct serialized into an array. - -**Deserialization Example:** - -```cairo -#[derive(Serde, Drop)] -struct A { - item_one: felt252, - item_two: felt252, -} - -#[executable] -fn main() { - let first_struct = A { item_one: 2, item_two: 99 }; - let mut output_array = array![]; - first_struct.serialize(ref output_array); - let mut span_array = output_array.span(); - let deserialized_struct: A = Serde::::deserialize(ref span_array).unwrap(); -} -``` - -## Serialization in Starknet Interactions - -When interacting with other contracts, such as using `library_call_syscall`, arguments must be serialized into a `Span`. The `Serde` trait is used for this serialization. The results of such calls are also serialized values that need to be deserialized. - -## Cairo VM Data Representation - -The Cairo VM fundamentally operates with `felt252` (252-bit field elements). - -- Data types that fit within 252 bits are represented by a single `felt252`. -- Data types larger than 252 bits are represented by a list of `felt252`. - -Therefore, to correctly formulate transaction calldata, especially for arguments larger than 252 bits, one must understand how to serialize them into lists of `felt252`. - -Starknet Serialization and Syscalls - -# Starknet Serialization and Syscalls - -No content available for this section. - -Serialization of Primitive and Composite Types - -# Serialization of Primitive and Composite Types - -## Data Types Using At Most 252 Bits - -The following Cairo data types are serialized as a single-member list containing one `felt252` value: - -- `ContractAddress` -- `EthAddress` -- `StorageAddress` -- `ClassHash` -- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`, `usize` -- `bytes31` -- `felt252` -- Signed integers: `i8`, `i16`, `i32`, `i64`, `i128` - - Negative values (`-x`) are serialized as `P-x`, where `P = 2^{251} + 17*2^{192} + 1`. - -## Data Types Using More Than 252 Bits - -These types have non-trivial serialization: - -- Unsigned integers larger than 252 bits: `u256`, `u512` -- Arrays and spans -- Enums -- Structs and tuples -- Byte arrays (strings) - -### Serialization of `u256` - -A `u256` value is represented by two `felt252` values: - -- The first `felt252` is the 128 least significant bits (low part). -- The second `felt252` is the 128 most significant bits (high part). - -Examples: - -- `u256(2)` serializes to `[2,0]`. -- `u256(2^{128})` serializes to `[0,1]`. -- `u256(2^{129} + 2^{128} + 20)` serializes to `[20,3]`. - -### Serialization of `u512` - -A `u512` value is a struct containing four `felt252` members, each representing a 128-bit limb. - -### Serialization of Arrays and Spans - -Arrays and spans are serialized as `, ,..., `. - -Example for `array![10, 20, u256(2^{128})]`: - -`[3, 10, 0, 20, 0, 0, 1]` - -### Serialization of Enums - -Enums are serialized as `,`. - -**Example 1:** - -```cairo -enum Week { - Sunday: (), - Monday: u256, -} -``` - -- `Week::Sunday` serializes to `[0]`. -- `Week::Monday(5)` serializes to `[1, 5, 0]`. - -**Example 2:** - -```cairo -enum MessageType { - A, - #[default] - B: u128, - C -} -``` - -- `MessageType::A` serializes to `[0]`. -- `MessageType::B(6)` serializes to `[1, 6]`. -- `MessageType::C` serializes to `[2]`. - -### Serialization of Structs and Tuples - -Structs and tuples are serialized by serializing their members sequentially in the order they appear in their definition. - -Example for `MyStruct { a: u256, b: felt252, c: Array }`: - -```cairo -struct MyStruct { - a: u256, - b: felt252, - c: Array -} -``` - -For `MyStruct { a: 2, b: 5, c: [1,2,3] }`, the serialization is `[2, 0, 5, 3, 1, 2, 3]`. - -### Serialization of Byte Arrays - -A byte array (string) consists of: - -- `data: Array`: Contains 31-byte chunks. -- `pending_word: felt252`: Remaining bytes (at most 30). -- `pending_word_len: usize`: Number of bytes in `pending_word`. - -**Example 1: String "hello" (5 bytes)** - -Serialization: `[0, 0x68656c6c6f, 5]` - -Simplifying Serialization with Starknet Tools - -No content is available for this section. - -Optimizing Storage and Bitwise Operations - -# Optimizing Storage and Bitwise Operations - -Optimizing storage usage in Cairo smart contracts is crucial for reducing gas costs, as storage updates are a significant contributor to transaction expenses. By packing multiple values into fewer storage slots, developers can decrease the gas cost for users. - -## Optimizing Storage Costs - -The core principle of storage optimization is **bit-packing**: using the minimum number of bits necessary to store data. This is particularly important in smart contracts where storage is expensive. Packing multiple variables into fewer storage slots directly reduces the gas cost associated with storage updates. - -## Integer Structure and Bitwise Operators - -An integer is represented by a specific number of bits (e.g., a `u8` uses 8 bits). Multiple integers can be combined into a larger integer type if the larger type's bit size is sufficient to hold the sum of the smaller integers' bit sizes (e.g., two `u8`s and one `u16` can fit into a `u32`). - -This packing and unpacking process relies on bitwise operators: - -- **Shifting:** Multiplying or dividing an integer by a power of 2 shifts its binary representation to the left or right, respectively. -- **Masking (AND operator):** Applying a mask isolates specific bits within an integer. -- **Combining (OR operator):** Adding two integers using the bitwise OR operator merges their bit patterns. - -These operators allow for the efficient packing of multiple smaller data types into a larger one and the subsequent unpacking of the packed data. - -## Bit-packing in Cairo - -Starknet's contract storage is a map with 2251 slots, each initialized to 0 and storing a `felt252`. To minimize gas costs, variables should be packed to occupy fewer storage slots. - -For example, a `Sizes` struct containing a `u8`, a `u32`, and a `u64` (totaling 104 bits) can be packed into a single `u128` (128 bits), which is less than a full storage slot. - -```cairo,noplayground -struct Sizes { - tiny: u8, - small: u32, - medium: u64, -} -``` - -### Packing and Unpacking Example - -To pack these variables into a `u128`, they are successively shifted left and summed. To unpack, they are successively shifted right and masked to isolate each value. - -### The `StorePacking` Trait - -Cairo provides the `StorePacking` trait to manage the packing and unpacking of struct fields into fewer storage slots. `T` is the type to be packed, and `PackedT` is the destination type. The trait requires implementing `pack` and `unpack` functions. - -Here's an implementation for the `Sizes` struct: - -```cairo,noplayground -use starknet::storage_access::StorePacking; - -#[derive(Drop, Serde)] -struct Sizes { - tiny: u8, - small: u32, - medium: u64, -} - -const TWO_POW_8: u128 = 0x100; -const TWO_POW_40: u128 = 0x10000000000; - -const MASK_8: u128 = 0xff; -const MASK_32: u128 = 0xffffffff; - -impl SizesStorePacking of StorePacking { - fn pack(value: Sizes) -> u128 { - value.tiny.into() + (value.small.into() * TWO_POW_8) + (value.medium.into() * TWO_POW_40) - } - - fn unpack(value: u128) -> Sizes { - let tiny = value & MASK_8; - let small = (value / TWO_POW_8) & MASK_32; - let medium = (value / TWO_POW_40); - - Sizes { - tiny: tiny.try_into().unwrap(), - small: small.try_into().unwrap(), - medium: medium.try_into().unwrap(), - } - } -} - -#[starknet::contract] -mod SizeFactory { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use super::Sizes; - use super::SizesStorePacking; //don't forget to import it! - - #[storage] - struct Storage { - remaining_sizes: Sizes, - } - - #[abi(embed_v0)] - fn update_sizes(ref self: ContractState, sizes: Sizes) { - // This will automatically pack the - // struct into a single u128 - self.remaining_sizes.write(sizes); - } - - - #[abi(embed_v0)] - fn get_sizes(ref self: ContractState) -> Sizes { - // this will automatically unpack the - // packed-representation into the Sizes struct - self.remaining_sizes.read() - } -} -``` - -The `StorePacking` trait, when implemented and used with storage `read` and `write` operations, automatically handles the packing and unpacking of the struct's data. - -Cairo Components - -Introduction to Cairo Components - -# Introduction to Cairo Components - -Cairo Components are modular add-ons that encapsulate reusable functionality, allowing developers to incorporate specific features into their contracts without reimplementing common logic. This approach separates core contract logic from additional functionalities, making development less painful and bug-prone. - -Defining and Implementing Cairo Components - -# Defining and Implementing Cairo Components - -Components in Cairo are modular pieces of logic, storage, and events that can be reused across multiple contracts. They function like Lego blocks, allowing you to extend a contract's functionality without duplicating code. A component is a separate module that cannot be deployed independently; its logic becomes part of the contract it's embedded into. - -## What's in a Component? - -A component is similar to a contract and can contain: - -- Storage variables -- Events -- External and internal functions - -However, a component cannot be deployed on its own. Its code is integrated into the contract that embeds it. - -## Creating Components - -To create a component: - -1. **Define the component module:** Decorate a module with `#[starknet::component]`. Within this module, declare a `Storage` struct and an `Event` enum. -2. **Define the component interface:** Declare a trait with the `#[starknet::interface]` attribute. This trait defines the signatures of functions accessible externally, enabling interaction via the dispatcher pattern. -3. **Implement the component logic:** Use an `impl` block marked with `#[embeddable_as(name)]` for the component's external logic. This `impl` block typically implements the interface trait. - -Internal functions, not meant for external access, can be defined in an `impl` block without the `#[embeddable_as]` attribute. These internal functions are usable within the embedding contract but are not part of its ABI. - -Functions within these `impl` blocks expect arguments like `ref self: ComponentState` for state-modifying functions or `self: @ComponentState` for view functions. This genericity over `TContractState` allows the component to be used in any contract. - -### Example: An Ownable Component - -**Interface:** - -```cairo,noplayground -#[starknet::interface] -trait IOwnable { - fn owner(self: @TContractState) -> ContractAddress; - fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); - fn renounce_ownership(ref self: TContractState); -} -``` - -**Component Definition:** - -```cairo,noplayground -#[starknet::component] -pub mod ownable_component { - use core::num::traits::Zero; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address}; - use super::Errors; - - #[storage] - pub struct Storage { - owner: ContractAddress, - } - - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - OwnershipTransferred: OwnershipTransferred, - } - - #[derive(Drop, starknet::Event)] - struct OwnershipTransferred { - previous_owner: ContractAddress, - new_owner: ContractAddress, - } - - #[embeddable_as(Ownable)] - impl OwnableImpl> of super::IOwnable> { - fn owner(self: @ComponentState) -> ContractAddress { - self.owner.read() - } - - 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); - } - - fn renounce_ownership(ref self: ComponentState) { - self.assert_only_owner(); - self._transfer_ownership(Zero::zero()); - } - } - - #[generate_trait] - pub impl InternalImpl> of InternalTrait { - fn initializer(ref self: ComponentState, owner: ContractAddress) { - self._transfer_ownership(owner); - } - - 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); - } - - 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 }, - ); - } - } -} -``` - -## A Closer Look at the `impl` Block - -The `#[embeddable_as(name)]` attribute marks an `impl` as embeddable and specifies the name used to refer to the component within a contract. The implementation is generic over `ComponentState`, requiring `TContractState` to implement `HasComponent`. This trait, automatically generated, bridges between the contract's state (`TContractState`) and the component's state (`ComponentState`), enabling access to component state via `get_component` and `get_component_mut`. - -The compiler generates an `#[starknet::embeddable]` impl that adapts the component's functions to use `TContractState` instead of `ComponentState`, making the component's interface directly callable from the contract. - -Access to storage and events within a component is handled through `ComponentState`, using methods like `self.storage_var_name.read()` or `self.emit(...)`. - -Integrating and Composing Cairo Components - -# Integrating and Composing Cairo Components - -Components allow for the reuse of existing logic within new contracts. They can also be composed, allowing one component to depend on another. - -## Integrating a Component into a Contract - -To integrate a component into your contract, follow these steps: - -1. **Declare the component:** Use the `component!()` macro, specifying the component's path, the name for its storage variable in the contract, and the name for its event variant. - ```cairo - component!(path: ownable_component, storage: ownable, event: OwnableEvent); - ``` -2. **Add storage and events:** Include the component's storage and events in the contract's `Storage` and `Event` definitions. The storage variable must be annotated with `#[substorage(v0)]`. - - ```cairo - #[storage] - struct Storage { - counter: u128, - #[substorage(v0)] - ownable: ownable_component::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - OwnableEvent: ownable_component::Event, - } - ``` - -3. **Embed the component's logic:** Use an impl alias annotated with `#[abi(embed_v0)]` to instantiate the component's generic impl with the contract's `ContractState`. This exposes the component's functions externally. - ```cairo - #[abi(embed_v0)] - impl OwnableImpl = ownable_component::Ownable; - ``` - -Interacting with the component's functions externally is done via a dispatcher instantiated with the contract's address. - -**Example of a contract integrating the `Ownable` component:** - -```cairo,noplayground -#[starknet::contract] -mod OwnableCounter { - use listing_01_ownable::component::ownable_component; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - component!(path: ownable_component, storage: ownable, event: OwnableEvent); - - #[abi(embed_v0)] - impl OwnableImpl = ownable_component::Ownable; - - impl OwnableInternalImpl = ownable_component::InternalImpl; - - #[storage] - struct Storage { - counter: u128, - #[substorage(v0)] - ownable: ownable_component::Storage, - } - - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - OwnableEvent: ownable_component::Event, - } - - - #[abi(embed_v0)] - fn foo(ref self: ContractState) { - self.ownable.assert_only_owner(); - self.counter.write(self.counter.read() + 1); - } -} -``` - -## Stacking Components and Dependencies - -Components can be composed by having one component depend on another. This is achieved by adding trait bounds to the component's `impl` block, specifying that it requires another component's `HasComponent` trait. - -### Specifying Dependencies - -A component can depend on another by adding a named trait bound for the dependency's `HasComponent` trait. - -```cairo,noplayground - impl OwnableCounter< - TContractState, - +HasComponent, - +Drop, - impl Owner: ownable_component::HasComponent, // Dependency specified here - > of super::IOwnableCounter> { - // ... - } -``` - -This `impl Owner: ownable_component::HasComponent` bound ensures that the `TContractState` type has access to the `ownable_component`. - -### Using Dependencies - -Once a dependency is specified, its functions and state can be accessed using macros: - -- `get_dep_component!(@self, DependencyName)`: For read-only access. -- `get_dep_component_mut!(@self, DependencyName)`: For mutable access. - -**Example of a component depending on `Ownable`:** +Tests for Starknet contracts can be written using `snforge_std` and can either deploy the contract or interact with its internal state for testing. ```cairo,noplayground -#[starknet::component] -mod OwnableCounterComponent { - use listing_03_component_dep::owner::ownable_component; - use listing_03_component_dep::owner::ownable_component::InternalImpl; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - #[storage] - pub struct Storage { - value: u32, - } - - #[embeddable_as(OwnableCounterImpl)] - impl OwnableCounter< - TContractState, - +HasComponent, - +Drop, - impl Owner: ownable_component::HasComponent, - > of super::IOwnableCounter> { - fn get_counter(self: @ComponentState) -> u32 { - self.value.read() - } - - fn increment(ref self: ComponentState) { - let ownable_comp = get_dep_component!(@self, Owner); // Accessing dependency - ownable_comp.assert_only_owner(); - self.value.write(self.value.read() + 1); - } - - fn transfer_ownership( - ref self: ComponentState, new_owner: ContractAddress, - ) { - // Direct call to dependency's function - self.transfer_ownership(new_owner); - } - } -} -``` - -## Key Takeaways - -- **Embeddable Impls:** Allow injecting component logic into contracts, modifying ABIs and adding entry points. -- **`component!()` Macro:** Simplifies component integration by declaring its path, storage, and event names. -- **`HasComponent` Trait:** Automatically generated by the compiler when a component is used, bridging the contract's state and the component's state. -- **Impl Aliases:** Used to instantiate generic component impls with a contract's concrete `ContractState`. -- **Component Dependencies:** Achieved via trait bounds on `impl` blocks, enabling composition by allowing components to leverage functionality from other components using `get_dep_component!` or `get_dep_component_mut!`. - -Cairo Circuits and Gate Operations - -# Cairo Circuits and Gate Operations - -## Evaluating a Circuit - -The evaluation of a circuit involves passing input signals through each gate to obtain output values. This can be done using the `eval` function with a specified modulus. - -```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(); -# } -``` - -## Retrieving Gate Outputs - -The value of any specific output or intermediate gate can be retrieved from the evaluation results using the `get_output` function, passing the `CircuitElement` instance of the desired gate. - -```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(); -# } -``` - -Cairo Procedural Macros - -# Cairo Procedural Macros - -Testing Cairo Components - -Testing Cairo Components - -# Testing Components - -Testing components differs from testing contracts because components cannot be deployed independently and lack a `ContractState` object. To test them, you can integrate them into a mock contract or directly invoke their methods using a concrete `ComponentState` object. - -## Testing the Component by Deploying a Mock Contract - -The most straightforward way to test a component is by embedding it within a mock contract solely for testing purposes. This allows you to test the component within a contract's context and use a Dispatcher to interact with its entry points. - -First, define the component. For example, a `CounterComponent`: - -```cairo, noplayground -#[starknet::component] -pub mod CounterComponent { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - #[storage] - pub struct Storage { - value: u32, - } - - #[embeddable_as(CounterImpl)] - impl Counter< - TContractState, +HasComponent, - > of super::ICounter> { - fn get_counter(self: @ComponentState) -> u32 { - self.value.read() - } - - fn increment(ref self: ComponentState) { - self.value.write(self.value.read() + 1); - } - } -} -``` - -Next, create a mock contract that embeds this component: - -```cairo, noplayground -#[starknet::contract] -mod MockContract { - use super::counter::CounterComponent; - - component!(path: CounterComponent, storage: counter, event: CounterEvent); - - #[storage] - struct Storage { - #[substorage(v0)] - counter: CounterComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - CounterEvent: CounterComponent::Event, - } - - #[abi(embed_v0)] - impl CounterImpl = CounterComponent::CounterImpl; -} -``` - -Define an interface for interacting with the mock contract: - -```cairo, noplayground -#[starknet::interface] -pub trait ICounter { - fn get_counter(self: @TContractState) -> u32; - fn increment(ref self: TContractState); -} -``` - -Finally, write tests by deploying the mock contract and calling its entry points: +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}; -```cairo, noplayground -use starknet::SyscallResultTrait; -use starknet::syscalls::deploy_syscall; -use super::MockContract; -use super::counter::{ICounterDispatcher, ICounterDispatcherTrait}; +fn owner() -> ContractAddress { + contract_address_const::<'owner'>() +} -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 } +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_constructor() { - let counter = setup_counter(); - assert_eq!(counter.get_counter(), 0); + let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); + + 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()); } #[test] -fn test_increment() { - let counter = setup_counter(); - counter.increment(); - assert_eq!(counter.get_counter(), 1); +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); } -``` -## Testing Components Without Deploying a Contract +#[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); +} -Components utilize genericity, allowing their logic and storage to be embedded in multiple contracts. When a contract embeds a component, a `HasComponent` trait is generated, making the component's methods accessible. By providing a concrete `TContractState` that implements `HasComponent` to the `ComponentState` struct, you can invoke component methods directly on this object without deploying a mock contract. +#[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(); +} -To achieve this, first define a type alias for a concrete `ComponentState` implementation. Using the `MockContract::ContractState` type from the previous 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(); -```caskell -type TestingState = CounterComponent::ComponentState; + // When + pizza_factory.make_pizza(); -// You can derive even `Default` on this type alias -impl TestingStateDefault of Default { - fn default() -> TestingState { - CounterComponent::component_state_for_testing() - } + // 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)]); } -``` - -This `TestingState` type alias represents a concrete instance of `ComponentState`. Since `MockContract` embeds `CounterComponent`, the methods defined in `CounterImpl` are now usable on a `TestingState` object. -Instantiate a `TestingState` object using `component_state_for_testing()`: - -```cairo, noplayground -# use CounterComponent::CounterImpl; -# use super::MockContract; -# use super::counter::CounterComponent; -# -# type TestingState = CounterComponent::ComponentState; -# -# // You can derive even `Default` on this type alias -# impl TestingStateDefault of Default { -# fn default() -> TestingState { -# CounterComponent::component_state_for_testing() -# } -# } -# #[test] -fn test_increment() { - let mut counter: TestingState = Default::default(); - - counter.increment(); - counter.increment(); - - assert_eq!(counter.get_counter(), 2); +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); } ``` -This method is more lightweight and allows testing internal component functions not trivially exposed externally. +Assertions and Test Verification + +# Assertions and Test Verification -Performance Testing and Analysis +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. -# Performance Testing and Analysis +## `assert_eq!` and `assert_ne!` Macros + +These macros provide a convenient way to test for equality (`assert_eq!`) or inequality (`assert_ne!`) between two values. They are preferred over using `assert!(left == right)` because they print the differing values upon failure, aiding in debugging. -To analyze performance profiles, run `go tool pprof -http=":8000" path/to/profile/output.pb.gz`. This command starts a web server for analysis. +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)]`. -Consider the following `sum_n` function and its test case: +**Example using `assert_eq!` and `assert_ne!`:** ```cairo, noplayground -fn sum_n(n: usize) -> usize { - let mut i = 0; - let mut sum = 0; - while i <= n { - sum += i; - i += 1; - } - sum +pub fn add_two(a: u32) -> u32 { + a + 2 } #[cfg(test)] @@ -9798,3845 +6129,3491 @@ mod tests { use super::*; #[test] - #[available_gas(2000000)] - fn test_sum_n() { - let result = sum_n(10); - assert!(result == 55, "result is not 55"); + fn it_adds_two() { + assert_eq!(4, add_two(2)); + } + + #[test] + fn wrong_check() { + assert_ne!(0, add_two(2)); } } ``` -After generating the trace file and profile output, `go tool pprof` provides useful information: +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`. -- **Function Calls**: The test includes one function call, representing the test function itself. Multiple calls to `sum_n` within the test function still count as one call because `snforge` simulates a contract call. +## `assert_lt!`, `assert_le!`, `assert_gt!`, and `assert_ge!` Macros -- **Cairo Steps**: The execution of the `sum_n` function uses 256 Cairo steps. -
- pprof number of steps -
+These macros are used for comparison tests: -Additional information such as memory holes and builtins usage is also available. The Cairo Profiler is under active development with plans for more features. +- `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. -Circuit Input Management +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. -# Circuit Input Management +**Example using comparison macros:** -After defining a circuit and its outputs, the next step is to assign values to each input. In Cairo, circuits operate with a 384-bit modulus, meaning a single `u384` value is represented as a fixed array of four `u96` values. +```cairo, noplayground +#[derive(Drop, Copy, Debug, PartialEq)] +struct Dice { + number: u8, +} -## Assigning Input Values +impl DicePartialOrd of PartialOrd { + fn lt(lhs: Dice, rhs: Dice) -> bool { + lhs.number < rhs.number + } -The `new_inputs` and `next` functions are used to manage circuit inputs. These functions return a variant of the `AddInputResult` enum, which indicates whether all inputs have been filled or if more are needed. + fn le(lhs: Dice, rhs: Dice) -> bool { + lhs.number <= rhs.number + } -```cairo, noplayground -pub enum AddInputResult { - /// All inputs have been filled. - Done: CircuitData, - /// More inputs are needed to fill the circuit instance's data. - More: CircuitInputAccumulator, + fn gt(lhs: Dice, rhs: Dice) -> bool { + lhs.number > rhs.number + } + + fn ge(lhs: Dice, rhs: Dice) -> bool { + lhs.number >= rhs.number + } +} + +#[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, + ); } ``` -The following example demonstrates initializing inputs `a` and `b` to 10 and 20, respectively, within a circuit that calculates `a * (a + b)`: +## Adding Custom Failure Messages -```cairo, noplayground -// 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::> {}; +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. - let add = circuit_add(a, b); - let mul = circuit_mul(a, add); +**Example with a custom message:** - let output = (mul,); +```cairo, noplayground + #[test] + fn it_adds_two() { + assert_eq!(4, add_two(2), "Expected {}, got add_two(2)={}", 4, add_two(2)); + } +``` - let mut inputs = output.new_inputs(); - inputs = inputs.next([10, 0, 0, 0]); - inputs = inputs.next([20, 0, 0, 0]); +This results in a more informative error message upon failure, such as "Expected 4, got add_two(2)=5". - let instance = inputs.done(); +Advanced Testing Techniques: Panics and Ignoring Tests - let bn254_modulus = TryInto::< - _, CircuitModulus, - >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) - .unwrap(); +# Advanced Testing Techniques: Panics and Ignoring Tests - let res = instance.eval(bn254_modulus).unwrap(); +## Handling Panics in Tests - let add_output = res.get_output(add); - let circuit_output = res.get_output(mul); +Cairo provides mechanisms to handle and test for panics, which are runtime errors that halt program execution. - 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'); +### `panic!` Macro - (add_output, circuit_output) -} +The `panic!` macro is a convenient way to halt execution and signal an error. It can accept a string literal as an argument, allowing for longer error messages than the 31-character limit of `panic_with_felt252`. +```cairo #[executable] fn main() { - eval_circuit(); + if true { + panic!("2"); + } + println!("This line isn't reached"); } ``` -Contract Upgradeability - -# Contract Upgradeability - -Starknet offers native contract upgradeability through a syscall that updates the contract's source code, eliminating the need for proxy patterns. - -## How Upgradeability Works in Starknet - -Understanding Starknet's upgradeability requires differentiating between a contract and its contract class. - -- **Contract Classes:** Represent the source code of a program. They are identified by a class hash. Multiple contracts can be instances of the same class. A class must be declared before a contract instance of that class can be deployed. -- **Contract Instances:** Deployed contracts that are associated with a specific class hash and have their own storage. - -## Replacing Contract Classes - -### The `replace_class_syscall` +### `panic_with_felt252` Function -The `replace_class` syscall enables a deployed contract to update its associated class hash. To implement this, an entry point in the contract should execute the `replace_class_syscall` with the new class hash. +For a more idiomatic approach, `panic_with_felt252` can be used. It takes a `felt252` value as an argument, making it a concise way to express intent, especially when a simple error code is sufficient. -```cairo,noplayground -use core::num::traits::Zero; -use starknet::{ClassHash, syscalls}; +```cairo +use core::panic_with_felt252; -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(); +#[executable] +fn main() { + panic_with_felt252(2); } ``` -Listing 17-3: Exposing `replace_class_syscall` to update the contract's class +## Testing for Panics with `should_panic` -If a contract is deployed without this explicit mechanism, its class hash can still be replaced using `library_call`. +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. -### OpenZeppelin's Upgradeable Component +Consider a `Guess` struct where the `new` method panics if the input value is out of range: -OpenZeppelin Contracts for Cairo provides the `Upgradeable` component, which can be integrated into a contract to facilitate upgradeability. This component relies on an audited library for a secure upgrade process. - -**Usage Example:** +```cairo +#[derive(Drop)] +struct Guess { + value: u64, +} -To restrict who can upgrade a contract, access control mechanisms like OpenZeppelin's `Ownable` component are commonly used. The following example integrates `UpgradeableComponent` with `OwnableComponent` to allow only the contract owner to perform upgrades. +pub trait GuessTrait { + fn new(value: u64) -> Guess; +} -```cairo,noplayground -#[starknet::contract] -mod UpgradeableContract { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_upgrades::UpgradeableComponent; - use openzeppelin_upgrades::interface::IUpgradeable; - use starknet::{ClassHash, ContractAddress}; +impl GuessImpl of GuessTrait { + fn new(value: u64) -> Guess { + if value < 1 || value > 100 { + panic!("Guess must be >= 1 and <= 100"); + } - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + Guess { value } + } +} +``` - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; +A test to verify this panic behavior would look like: - // Upgradeable - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; +```cairo +#[cfg(test)] +mod tests { + use super::*; - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, + #[test] + #[should_panic] + fn greater_than_100() { + GuessTrait::new(200); } +} +``` - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, - } +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". - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.ownable.initializer(owner); +### Precise `should_panic` Tests + +To make `#[should_panic]` tests more robust, you can specify an `expected` string. The test will only pass if the panic message contains the specified text. + +```cairo +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic(expected: "Guess must be <= 100")] + fn greater_than_100() { + GuessTrait::new(200); } +} +``` - #[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(); +If the panic message does not match the `expected` string, the test will fail, indicating the mismatch. - // Replace the class hash upgrading the contract - self.upgradeable.upgrade(new_class_hash); - } +## Ignoring Tests + +Tests that are time-consuming or not relevant for regular runs can be ignored using the `#[ignore]` attribute. + +```cairo +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + // ... + } + + #[test] + #[ignore] + fn expensive_test() { // code that takes an hour to run } } ``` -Listing 17-4 Integrating OpenZeppelin's Upgradeable component in a contract +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. -The `UpgradeableComponent` offers: +## Running Single Tests -- An internal `upgrade` function for safe class replacement. -- An `Upgraded` event emitted upon successful upgrade. -- Protection against upgrading to a zero class hash. +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. -## Security Considerations +```shell +$ scarb test add_two_and_two +``` -Upgrades are critical operations requiring careful security review: +This command will execute only the `add_two_and_two` test. The output will indicate tests that were filtered out. -- **API Changes:** Modifications to function signatures (e.g., arguments) can break integrations with other contracts or off-chain systems. -- **Storage Changes:** Altering storage variable names, types, or organization can lead to data loss or corruption. Ensure storage slots are managed carefully (e.g., by prepending component names to variables). -- **Storage Collisions:** Avoid reusing storage slots, especially when integrating multiple components. -- **Backward Compatibility:** Verify backward compatibility when upgrading between different versions of OpenZeppelin Contracts. +Test Organization: Unit vs. Integration Tests -L1-L2 Messaging +# Test Organization: Unit vs. Integration Tests -Understanding L1-L2 Messaging in StarkNet +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. -# Understanding L1-L2 Messaging in StarkNet +## Unit Tests -StarkNet features a distinct L1-L2 messaging system, separate from its consensus and state update mechanisms. This system enables smart contracts on L1 to interact with L2 contracts, and vice versa, facilitating cross-chain transactions. For instance, computations performed on one chain can be utilized on the other. +Unit tests focus on verifying individual units of code in isolation. This allows for quick pinpointing of issues within a specific module or function. -## Use Cases +### Location and Structure -Bridges on StarkNet heavily rely on L1-L2 messaging. Depositing tokens into an L1 bridge contract automatically triggers the minting of the same token on L2. DeFi pooling is another significant application. +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]]`. -## Key Characteristics +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. -StarkNet's messaging system is characterized by being: +**Example Unit Test:** -- **Asynchronous**: Contracts cannot await message results from the other chain during their execution. -- **Asymmetric**: - - **L1 to L2**: The StarkNet sequencer automatically delivers messages to the target L2 contract. - - **L2 to L1**: Only the message hash is sent to L1 by the sequencer. Manual consumption via an L1 transaction is required. +```cairo +pub fn add(left: usize, right: usize) -> usize { + left + right +} -## The StarknetMessaging Contract +#[cfg(test)] +mod tests { + use super::*; -The StarknetMessaging contract is central to this system. + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} +``` -L1 to L2 Communication Flow +## Integration Tests -# L1 to L2 Communication Flow +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. -The `StarknetCore` contract on Ethereum, specifically its `StarknetMessaging` component, facilitates communication between L1 and L2. The `StarknetMessaging` contract adheres to the `IStarknetMessaging` interface, which defines functions for sending messages to L2, consuming messages from L2 on L1, and managing message cancellations. +### The `tests` Directory -```js -interface IStarknetMessaging is IStarknetMessagingEvents { +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. - function sendMessageToL2( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload - ) external returns (bytes32); +**Directory Structure Example:** - function consumeMessageFromL2(uint256 fromAddress, uint256[] calldata payload) - external - returns (bytes32); +```shell +adder +├── Scarb.lock +├── Scarb.toml +├── src +│ └── lib.cairo +└── tests + └── integration_test.cairo +``` - function startL1ToL2MessageCancellation( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload, - uint256 nonce - ) external; +**Example Integration Test:** - function cancelL1ToL2Message( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload, - uint256 nonce - ) external; +To test a function `add_two` from the `adder` crate: + +```cairo +// tests/integration_tests.cairo +use adder::add_two; + +#[test] +fn it_adds_two() { + assert_eq!(4, add_two(2)); } ``` -## Sending Messages from Ethereum to Starknet +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. -To send messages from Ethereum (L1) to Starknet (L2), your Solidity contracts must invoke the `sendMessageToL2` function of the `StarknetMessaging` contract. This involves specifying the target L2 contract address, the function selector (which must be annotated with `#[l1_handler]` on L2), and the message payload as an array of `uint256` (representing `felt252`). +### Running and Filtering Tests -A minimum of 20,000 wei must be sent with the transaction to cover the cost of registering the message hash on Ethereum. Additionally, sufficient fees must be paid for the `L1HandlerTransaction` executed by the Starknet sequencer to process the message on L2. +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. -The sequencer monitors logs from the `StarknetMessaging` contract. Upon detecting a message, it constructs and executes an `L1HandlerTransaction` to call the specified function on the target L2 contract. This process typically takes 1-2 minutes. +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. -### Example: Sending a Single Felt +### Organizing Integration Tests with Submodules -```js -// Sends a message on Starknet with a single felt. -function sendMessageFelt( - uint256 contractAddress, - uint256 selector, - uint256 myFelt -) - external - payable -{ - // We "serialize" here the felt into a payload, which is an array of uint256. - uint256[] memory payload = new uint256[](1); - payload[0] = myFelt; +As integration tests grow, you can organize them into multiple files within the `tests` directory. Each file is compiled as a separate crate. - // msg.value must always be >= 20_000 wei. - _snMessaging.sendMessageToL2{value: msg.value}( - contractAddress, - selector, - payload - ); +**Example with a common helper:** + +If you create `tests/common.cairo` with a `setup` function: + +```cairo +// tests/common.cairo +pub fn setup() { + println!("Setting up tests..."); } ``` -### Receiving Messages on Starknet - -On the Starknet side, functions intended to receive L1 messages must be marked with the `#[l1_handler]` attribute. The payload data is automatically deserialized into the appropriate Cairo types. +And `tests/integration_tests.cairo` calls it: ```cairo - #[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'); +// tests/integration_tests.cairo +use tests::common::setup; +use adder::it_adds_two; - // You can now use the data, automatically deserialized from the message payload. - assert(my_felt == 123, 'Invalid value'); - } +#[test] +fn internal() { + setup(); // Call helper function + assert!(it_adds_two(2, 2) == 4, "internal_adder failed"); +} ``` -L2 to L1 Communication Flow +Running `scarb test` would produce a section for `common.cairo` even if it contains no tests. -# L2 to L1 Communication Flow +To treat the entire `tests` directory as a single crate, you can add a `tests/lib.cairo` file: -When sending messages from Starknet (L2) to Ethereum (L1), the `send_message_to_l1_syscall` is used in Cairo contracts. This syscall includes the message parameters in the proof's output, making them accessible to the `StarknetCore` contract on L1 once the state update is processed. +```cairo +// tests/lib.cairo +mod common; +mod integration_tests; +``` -## Sending Messages from Starknet +This structure consolidates the tests into a single crate, allowing helper functions like `setup` to be imported and used without generating a separate output section for the helper file itself. The `scarb test` output will then reflect a single `adder_tests` crate. -The `send_message_to_l1_syscall` function has the following signature: +## Summary -```cairo,noplayground -pub extern fn send_message_to_l1_syscall( - to_address: felt252, payload: Span, -) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; -``` +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. -It takes the recipient's L1 address (`to_address`) and the message payload (`payload`) as arguments. +Testing Cairo Components -**Example:** +# Testing Cairo Components -```cairo,noplayground -let payload = ArrayTrait::new(); -payload.append(1); -payload.append(2); -send_message_to_l1_syscall(payload.span(), 3423542542364363).unwrap_syscall(); -``` +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. -## Consuming Messages on L1 +## Testing the Component by Deploying a Mock Contract -Messages sent from L2 to L1 must be consumed manually on L1. This involves a Solidity contract calling the `consumeMessageFromL2` function of the `StarknetMessaging` contract. The L2 contract address (which is the `to_address` used in the L2 syscall) and the payload must be passed to this function. +The most straightforward method to test a component is by integrating it into a mock contract solely for testing. This allows testing the component within a contract's context and using a Dispatcher to call its entry points. -The `consumeMessageFromL2` function verifies the message integrity. The `StarknetCore` contract uses `msg.sender` to compute the message hash, which must match the `to_address` provided during the L2 `send_message_to_l1_syscall`. +### Counter Component Example -**Example of consuming a message in Solidity:** +Consider a simple `CounterComponent` that allows incrementing a counter: -```js -function consumeMessageFelt( - uint256 fromAddress, - uint256[] calldata payload -) - external -{ - let messageHash = _snMessaging.consumeMessageFromL2(fromAddress, payload); +```cairo, noplayground +#[starknet::component] +pub mod CounterComponent { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - // We expect the payload to contain only a felt252 value (which is a uint256 in Solidity). - require(payload.length == 1, "Invalid payload"); + #[storage] + pub struct Storage { + value: u32, + } - uint256 my_felt = payload[0]; + #[embeddable_as(CounterImpl)] + pub impl Counter> of super::ICounter> { + fn get_counter(self: @ComponentState) -> u32 { + self.value.read() + } - // From here, you can safely use `my_felt` as the message has been verified by StarknetMessaging. - require(my_felt > 0, "Invalid value"); + fn increment(ref self: ComponentState) { + self.value.write(self.value.read() + 1); + } + } } ``` -Message Data and External Integration +### Mock Contract Definition -# Message Data and External Integration +A mock contract for testing the `CounterComponent` can be defined as follows: -## Message Serialization +```cairo, noplayground +#[starknet::contract] +mod MockContract { + use super::counter::CounterComponent; -Cairo contracts process serialized data exclusively as arrays of `felt252`. Since `felt252` is slightly smaller than Solidity's `uint256`, values exceeding the `felt252` maximum limit will result in stuck messages. + component!(path: CounterComponent, storage: counter, event: CounterEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + counter: CounterComponent::Storage, + } -A `uint256` in Cairo is represented by a struct containing two `u128` fields: `low` and `high`. Consequently, a single `uint256` value must be serialized into two `felt252` values. + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + CounterEvent: CounterComponent::Event, + } -```cairo,does_not_compile -struct u256 { - low: u128, - high: u128, + #[abi(embed_v0)] + impl CounterImpl = CounterComponent::CounterImpl; } ``` -For example, to send the value 1 as a `uint256` to Cairo (where `low = 1` and `high = 0`), the payload from L1 would include two values: +This mock contract embeds the component and exposes its entry points. An interface is also defined for external interaction: -```js -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; +```cairo, noplayground +#[starknet::interface] +pub trait ICounter { + fn get_counter(self: @TContractState) -> u32; + fn increment(ref self: TContractState); +} ``` -For further details on the messaging mechanism, refer to the [Starknet documentation][starknet messaging doc] and the [detailed guide here][glihm messaging guide]. - -## Price Feeds +Tests can then be written by deploying this mock contract and calling its entry points: -Price feeds, powered by oracles, integrate real-world pricing data into the blockchain. This data is aggregated from multiple trusted external sources, such as cryptocurrency exchanges and financial data providers. - -This section will use Pragma Oracle to demonstrate reading the `ETH/USD` price feed and showcase a mini-application utilizing this data. +```cairo, noplayground +use starknet::SyscallResultTrait; +use starknet::syscalls::deploy_syscall; +use super::MockContract; +use super::counter::{ICounterDispatcher, ICounterDispatcherTrait}; -[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 +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 } +} -Oracles and Randomness +#[test] +fn test_constructor() { + let counter = setup_counter(); + assert_eq!(counter.get_counter(), 0); +} -Oracle Integration for Price Feeds +#[test] +fn test_increment() { + let counter = setup_counter(); + counter.increment(); + assert_eq!(counter.get_counter(), 1); +} +``` -# Oracle Integration for Price Feeds +## Testing Components Without Deploying a Contract -[Pragma Oracle](https://www.pragma.build/) is a zero-knowledge oracle that provides verifiable off-chain data on the Starknet blockchain. +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. -## Setting Up Your Contract for Price Feeds +### Using Type Aliases for Testing -### Add Pragma as a Project Dependency +Define a type alias for `ComponentState` using a concrete `ContractState` (e.g., `MockContract::ContractState`): -To integrate Pragma into your Cairo smart contract, add the following to your project's `Scarb.toml` file: +```cairo, noplayground +type TestingState = CounterComponent::ComponentState; -```toml -[dependencies] -pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } -``` +impl TestingStateDefault of Default { + fn default() -> TestingState { + CounterComponent::component_state_for_testing() + } +} -### Creating a Price Feed Contract +#[test] +fn test_increment() { + let mut counter: TestingState = Default::default(); -Define a contract interface that includes the necessary Pragma price feed entry point. The `get_asset_price` function is crucial for interacting with the Pragma oracle. + counter.increment(); + counter.increment(); -```cairo,noplayground -#[starknet::interface] -pub trait IPriceFeedExample { - fn buy_item(ref self: TContractState); - fn get_asset_price(self: @TContractState, asset_id: felt252) -> u128; + assert_eq!(counter.get_counter(), 2); } ``` -### Import Pragma Dependencies +This `TestingState` type alias allows direct invocation of component methods. The `component_state_for_testing` function creates an instance of `TestingState` for testing. -Include the following imports in your contract module: +This approach is more lightweight and allows testing internal component functions not trivially exposed externally. -```cairo,noplayground -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}; - -const ETH_USD: felt252 = 19514442401534788; -const EIGHT_DECIMAL_FACTOR: u256 = 100000000; -``` +## Testing Contract Internals -### Required Price Feed Function Implementation +### Accessing Internal Functions with `contract_state_for_testing` -The `get_asset_price` function retrieves the asset's price from Pragma Oracle. It calls `get_data_median` with `DataType::SpotEntry(asset_id)` and returns the price. +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. ```cairo,noplayground -fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 { - // Retrieve the oracle dispatcher - let oracle_dispatcher = IPragmaABIDispatcher { - contract_address: self.pragma_contract.read(), - }; - - // Call the Oracle contract, for a spot entry - let output: PragmaPricesResponse = oracle_dispatcher - .get_data_median(DataType::SpotEntry(asset_id)); +use crate::pizza::PizzaFactory::{InternalTrait}; +use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; - return output.price; +#[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); } ``` -## Example Application Using Pragma Price Feed - -The following contract demonstrates how to use the Pragma oracle to fetch the ETH/USD price and use it in a transaction. +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. -```cairo,noplayground -#[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}; +### Mocking Caller Address with `start_cheat_caller_address` - const ETH_USD: felt252 = 19514442401534788; - const EIGHT_DECIMAL_FACTOR: u256 = 100000000; +The `start_cheat_caller_address` function allows mocking the caller's address to test access control logic. - #[storage] - struct Storage { - pragma_contract: ContractAddress, - product_price_in_usd: u256, - } +```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(); - #[constructor] - fn constructor(ref self: ContractState, pragma_contract: ContractAddress) { - self.pragma_contract.write(pragma_contract); - self.product_price_in_usd.write(100); - } + let new_owner: ContractAddress = contract_address_const::<'new_owner'>(); + assert_eq!(pizza_factory.get_owner(), owner()); - #[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, - ); - } + start_cheat_caller_address(pizza_factory_address, owner()); - fn get_asset_price(self: @ContractState, asset_id: felt252) -> u128 { - // Retrieve the oracle dispatcher - let oracle_dispatcher = IPragmaABIDispatcher { - contract_address: self.pragma_contract.read(), - }; + pizza_factory.change_owner(new_owner); - // Call the Oracle contract, for a spot entry - let output: PragmaPricesResponse = oracle_dispatcher - .get_data_median(DataType::SpotEntry(asset_id)); + assert_eq!(pizza_factory.get_owner(), new_owner); +} - return output.price; - } - } +#[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); } ``` -Verifiable Randomness with Oracles +### Capturing Events with `spy_events` -# Verifiable Randomness with Oracles +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. -Generating truly unpredictable randomness on-chain is challenging due to the deterministic nature of blockchains. Verifiable Random Functions (VRFs) provided by oracles offer a solution, guaranteeing that randomness cannot be predicted or tampered with, which is crucial for applications like gaming and NFTs. +```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); -## Overview on VRFs + pizza_factory.make_pizza(); +} -VRFs use a secret key and a nonce to generate an output that appears random. While technically pseudo-random, it's practically impossible to predict without the secret key. VRFs also produce a proof that allows anyone to verify the correctness of the generated random number. +#[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(); -## Generating Randomness with Pragma + // When + pizza_factory.make_pizza(); -[Pragma](https://www.pragma.build/), an oracle on Starknet, provides a solution for generating random numbers using VRFs. + // 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)]); +} +``` -### Add Pragma as a Dependency +Testing Smart Contracts with Frameworks -To use Pragma, add it to your `Scarb.toml` file: +## Testing Smart Contracts with Frameworks -```toml -[dependencies] -pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } -``` +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. -### Define the Contract Interface +### Configuring Scarb Project with Starknet Foundry -The following interfaces are used for Pragma VRF and a simple dice game: +Modify your `Scarb.toml` file to include Starknet Foundry: -```cairo,noplayground -use starknet::ContractAddress; +```toml,noplayground +[dev-dependencies] +snforge_std = "0.48.0" # Use the latest version -#[starknet::interface] -pub trait IPragmaVRF { - fn get_last_random_number(self: @TContractState) -> felt252; - fn request_randomness_from_pragma( - ref self: TContractState, - seed: u64, - callback_address: ContractAddress, - callback_fee_limit: u128, - publish_delay: u64, - num_words: u64, - calldata: Array, - ); - fn receive_random_words( - ref self: TContractState, - requester_address: ContractAddress, - request_id: u64, - random_words: Span, - calldata: Array, - ); - fn withdraw_extra_fee_fund(ref self: TContractState, receiver: ContractAddress); -} +[scripts] +test = "snforge test" -#[starknet::interface] -pub trait IDiceGame { - fn guess(ref self: TContractState, guess: u8); - fn toggle_play_window(ref self: TContractState); - fn get_game_window(self: @TContractState) -> bool; - fn process_game_winners(ref self: TContractState); -} +[tool.scarb] +allow-pre-built-plugins = ["snforge_std"] ``` -### Description of Key `IPragmaVRF` Entrypoints and Their Inputs +This configuration ensures that running `scarb test` will execute `snforge test`. After configuring `Scarb.toml`, install Starknet Foundry following the official documentation. + +### Testing Flow with Starknet Foundry -The `request_randomness_from_pragma` function initiates a request for verifiable randomness. It emits an event that triggers off-chain actions: randomness generation and on-chain submission via the `receive_random_words` callback. +The typical workflow for testing a contract with Starknet Foundry involves these steps: -#### `request_randomness_from_pragma` Inputs: +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. -- `seed`: A unique value to initialize randomness generation. -- `callback_address`: The contract address for the `receive_random_words` callback. -- `callback_fee_limit`: Maximum gas for the callback execution. -- `publish_delay`: Minimum delay (in blocks) before fulfilling the request. -- `num_words`: The number of random values to receive. -- `calldata`: Additional data for the callback. +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. -#### `receive_random_words` Inputs: +Profiling and Performance Analysis -- `requester_address`: The contract address that requested randomness. -- `request_id`: A unique identifier for the request. -- `random_words`: An array of generated random values. -- `calldata`: Data passed with the initial request. +### Profiling and Performance Analysis -### Dice Game Contract +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. -A simple dice game contract example utilizes Pragma VRF. +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. -#### NB: Fund Your Contract After Deployment to Utilize Pragma VRF +Consider the following `sum_n` function and its test: -After deploying your contract, ensure it has sufficient ETH to cover the costs of generating random numbers and executing the callback function. For more details, refer to the [Pragma docs](https://docs.pragma.build/Resources/Starknet/randomness/randomness). +```cairo +fn sum_n(n: usize) -> usize { + let mut i = 0; + let mut sum = 0; + while i <= n { + sum += i; + i += 1; + } + sum +} -Oracles and Contract Funding +#[cfg(test)] +mod tests { + use super::*; -# Oracles and Contract Funding + #[test] + #[available_gas(2000000)] + fn test_sum_n() { + let result = sum_n(10); + assert!(result == 55, "result is not 55"); + } +} +``` -Starknet Development Tools +After generating the trace and profile output, the `go tool pprof` web server provides valuable information: -# Starknet Development Tools +- **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. -This section covers useful development tools provided by the Cairo project and Starknet ecosystem. +
+ pprof number of steps +
-## Compiler Diagnostics +The Cairo Profiler also offers insights into memory holes and builtins usage, with ongoing development for additional features. -The Cairo compiler provides helpful diagnostics for common errors: +[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/ -- **`Plugin diagnostic: name is not a substorage member in the contract's Storage. Consider adding to Storage:`**: This error indicates that a component's storage was not added to the contract's storage. To fix this, add the path to the component's storage, annotated with `#[substorage(v0)]`, to your contract's storage. -- **`Plugin diagnostic: name is not a nested event in the contract's Event enum. Consider adding to the Event enum:`**: Similar to the storage error, this means a component's events were not added to the contract's events. Ensure the path to the component's events is included in your contract's events. +Quizzes and Summaries -## Automatic Formatting with `scarb fmt` +# Quizzes and Summaries -Scarb projects can be automatically 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. +### How to Write Tests -To format a Cairo project, navigate to the project directory and run: +1. **What is the annotation you add to a function to indicate that it's a test?** + `#[test]` -```bash -scarb fmt -``` +2. **Let's say you have a function with the type signature:** -To exclude specific code sections from formatting, use the `#[cairofmt::skip]` attribute: + ```cairo + fn f(x: usize) -> Result; + ``` -```cairo, noplayground -#[cairofmt::skip] -let table: Array = array![ - "oxo", - "xox", - "oxo", -]; -``` + **And you want to test that `f(0)` should return `Err(_)`. Which of the following is _NOT_ a valid way to test that?** -## IDE Integration Using `cairo-language-server` + ```cairo + #[test] + #[should_err] + fn test() -> Result { + f(0) + } + ``` -The `cairo-language-server` is recommended for integrating Cairo with Integrated Development Environments (IDEs). It implements the Language Server Protocol (LSP), enabling communication between IDEs and programming languages. This server powers features like autocompletion, jump-to-definition, and inline error display in IDEs such as Visual Studio Code (via the `vscode-cairo` extension). + _Note: `should_err` does not exist in Cairo — tests that return `Result` will pass even if the result is an `Err`._ -If you have Scarb installed, the Cairo VSCode extension should work out-of-the-box without manual installation of the language server. +3. **Does the test pass?** -## Local Starknet Node with `katana` + ```cairo + fn division_operation(number1: u16, number2: u16) -> u16 { + if number2 == 0 { + panic!("ZeroDivisionError not allowed!"); + } + let result = number1 / number2; + result + } -`katana` is a tool that starts a local Starknet node with predeployed accounts. These accounts can be used for deploying and interacting with contracts. + #[cfg(test)] + mod tests { + use super::{division_operation}; -```bash -# Example command to start katana (specific command may vary) -# katana [options] -``` + #[test] + #[should_panic(expected: ("Zerodivisionerror not allowed!",))] + fn test_division_operation() { + division_operation(10, 0); + } + } + ``` -The output of `katana` typically lists prefunded accounts with their addresses, private keys, and public keys. Before interacting with contracts, voter accounts need to be registered and funded. For detailed information on account operations and Account Abstraction, refer to the Starknet documentation. + **No**. The expected string `"Zerodivisionerror not allowed!"` should be exactly the same as the panic string `"ZeroDivisionError not allowed!"`. -## Interacting with Starknet using `starkli` +4. **What is the output when these tests are run with the command `scarb cairo-test -f test_`?** -`starkli` is a command-line tool for interacting with Starknet. Ensure your `starkli` version matches the required version (e.g., `0.3.6`). You can upgrade `starkli` using `starkliup`. + ```cairo + #[cfg(test)] + mod tests { + #[test] + #[ignore] + fn test_addition() { + assert_ne!((5 + 4), 5); + } -### Smart Wallets + #[test] + fn division_function() { + assert_eq!((10_u8 / 5), 2); + } -You can retrieve the smart wallet class hash using: + #[test] + fn test_multiplication() { + assert_ne!((3 * 2), 8); + assert_eq!((5 * 5), 25); + } -```bash -starkli class-hash-at --rpc http://0.0.0.0:5050 -``` + #[test] + fn test_subtraction() { + assert!((12 - 11) == 1, "The first argument was false"); + } + } + ``` -### Contract Deployment + `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.* -Before deploying, contracts must be declared using `starkli declare`: +Starknet Smart Contracts -```bash -starkli declare target/dev/listing_99_12_vote_contract_Vote.contract_class.json --rpc http://0.0.0.0:5050 --account katana-0 -``` +Introduction to Starknet and Smart Contracts -- The `--rpc` flag specifies the RPC endpoint (e.g., provided by `katana`). -- The `--account` flag specifies the account for signing transactions. -- If encountering `compiler-version` errors, use the `--compiler-version x.y.z` flag or upgrade `starkli`. +# Introduction to Starknet and Smart Contracts -The class hash for a contract might look like: `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52`. Transactions on local nodes finalize immediately, while on testnets, finality may take a few seconds. +## What are Smart Contracts? -ERC20 Token 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. -# ERC20 Token Contracts +### Programming Languages and Compilation -The ERC20 standard on Starknet provides a uniform interface for fungible tokens, ensuring predictable interactions across the ecosystem. OpenZeppelin Contracts for Cairo offers an audited implementation of this standard. +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). -## The Basic ERC20 Contract +### Characteristics of Smart Contracts -This contract demonstrates the core structure for creating a token with a fixed supply using OpenZeppelin's components. +- **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. -```cairo,noplayground -#[starknet::contract] -pub mod BasicERC20 { - use openzeppelin_token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; +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. - component!(path: ERC20Component, storage: erc20, event: ERC20Event); +## Use Cases for Smart Contracts - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; +Smart contracts enable a wide range of applications: - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage, - } +### Decentralized Finance (DeFi) - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event, - } +Enables financial applications like lending/borrowing, decentralized exchanges (DEXs), stablecoins, and more, without traditional intermediaries. - #[constructor] - fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { - let name = "MyToken"; - let symbol = "MTK"; +### Tokenization - self.erc20.initializer(name, symbol); - self.erc20.mint(recipient, initial_supply); - } -} -``` +Facilitates the creation and trading of digital tokens representing real-world assets (e.g., real estate, art), enabling fractional ownership and increased liquidity. -This contract embeds the `ERC20Component` for core ERC20 logic. The constructor initializes the token's name and symbol and mints the initial supply to the deployer, resulting in a fixed total supply. +### Voting -### Mintable and Burnable Token +Creates secure, transparent, and immutable voting systems where votes are tallied automatically on the blockchain. -This extension adds functions to mint new tokens and burn existing ones, allowing the token supply to change after deployment. It utilizes `OwnableComponent` for access control. +### Royalties -```cairo,noplayground -#[starknet::contract] -pub mod MintableBurnableERC20 { - use openzeppelin_access::ownable::OwnableComponent; - use openzeppelin_token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; +Automates the distribution of royalties to content creators based on consumption or sales. - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); +### Decentralized Identities (DIDs) - // Ownable Mixin - #[abi(embed_v0)] - impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; +Allows individuals to manage their digital identities securely and control the sharing of personal information. - // ERC20 Mixin - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; +## The Rise of Starknet and Cairo - #[storage] - struct Storage { - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage, - } +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. - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - } +### Cairo and Starknet's VM - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - let name = "MintableBurnableToken"; - let symbol = "MBT"; +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. - self.erc20.initializer(name, symbol); - self.ownable.initializer(owner); - } +## Cairo Programs vs. Starknet Smart Contracts - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - // Only owner can mint new tokens - self.ownable.assert_only_owner(); - self.erc20.mint(recipient, amount); - } +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. + +### Smart Wallets and Account Abstraction - #[external(v0)] - fn burn(ref self: ContractState, amount: u256) { - // Any token holder can burn their own tokens - let caller = starknet::get_caller_address(); - self.erc20.burn(caller, amount); - } -} -``` +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. -The `mint` function is restricted to the owner, allowing them to increase the total supply. The `burn` function enables any token holder to reduce the supply by destroying their tokens. +Starknet Contract Fundamentals -### Pausable Token with Access Control +# Starknet Contract Fundamentals -This implementation adds a security model with role-based permissions and an emergency pause feature using `AccessControlComponent`, `PausableComponent`, and `SRC5Component`. +## Defining a Starknet Contract -```cairo,noplayground -#[starknet::contract] -pub mod PausableERC20 { - use openzeppelin_access::accesscontrol::AccessControlComponent; - use openzeppelin_introspection::src5::SRC5Component; - use openzeppelin_security::pausable::PausableComponent; - use openzeppelin_token::erc20::{DefaultConfig, ERC20Component}; - use starknet::ContractAddress; - - const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE"); - const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); - - component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - component!(path: PausableComponent, storage: pausable, event: PausableEvent); - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - // AccessControl - #[abi(embed_v0)] - impl AccessControlImpl = - AccessControlComponent::AccessControlImpl; - impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; +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]`. - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; +## Anatomy of a Simple Contract - // Pausable - #[abi(embed_v0)] - impl PausableImpl = PausableComponent::PausableImpl; - impl PausableInternalImpl = PausableComponent::InternalImpl; +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. - // ERC20 - #[abi(embed_v0)] - impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; +```cairo,noplayground +#[starknet::interface] +trait ISimpleStorage { + fn set(ref self: TContractState, x: u128); + fn get(self: @TContractState) -> u128; +} + +#[starknet::contract] +mod SimpleStorage { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { - #[substorage(v0)] - accesscontrol: AccessControlComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage, - #[substorage(v0)] - pausable: PausableComponent::Storage, - #[substorage(v0)] - erc20: ERC20Component::Storage, + stored_data: u128, } - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - AccessControlEvent: AccessControlComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event, - #[flat] - PausableEvent: PausableComponent::Event, - #[flat] - ERC20Event: ERC20Component::Event, - } + #[abi(embed_v0)] + impl SimpleStorage of super::ISimpleStorage { + fn set(ref self: ContractState, x: u128) { + self.stored_data.write(x); + } - // ERC20 Hooks implementation - impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { - fn before_update ( - ref self: ERC20Component::ComponentState, - from: ContractAddress, - recipient: ContractAddress, - amount: u256, - ) { - let contract_state = self.get_contract(); - // Check that the contract is not paused - contract_state.pausable.assert_not_paused(); + fn get(self: @ContractState) -> u128 { + self.stored_data.read() } } +} +``` - #[constructor] - fn constructor(ref self: ContractState, admin: ContractAddress) { - let name = "PausableToken"; - let symbol = "PST"; +### The Interface: The Contract's Blueprint - self.erc20.initializer(name, symbol); +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). - // Grant admin role - self.accesscontrol.initializer(); - self.accesscontrol._grant_role(AccessControlComponent::DEFAULT_ADMIN_ROLE, admin); +```cairo,noplayground +#[starknet::interface] +trait ISimpleStorage { + fn set(ref self: TContractState, x: u128); + fn get(self: @TContractState) -> u128; +} +``` - // Grant specific roles to admin - self.accesscontrol._grant_role(PAUSER_ROLE, admin); - self.accesscontrol._grant_role(MINTER_ROLE, admin); - } +## Public Functions - #[external(v0)] - fn pause(ref self: ContractState) { - self.accesscontrol.assert_only_role(PAUSER_ROLE); - self.pausable.pause(); - } +Public functions are exposed externally. They are defined within an `impl` block annotated with `#[abi(embed_v0)]` or as standalone functions with `#[external(v0)]`. - #[external(v0)] - fn unpause(ref self: ContractState) { - self.accesscontrol.assert_only_role(PAUSER_ROLE); - self.pausable.unpause(); - } +- **External Functions**: Use `ref self: ContractState`, allowing state modification. +- **View Functions**: Use `@self: ContractState`, restricting state modification through `self` at compile time. - #[external(v0)] - fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - self.accesscontrol.assert_only_role(MINTER_ROLE); - self.erc20.mint(recipient, amount); +```cairo,noplayground + #[abi(embed_v0)] + impl SimpleStorage of super::ISimpleStorage { + fn set(ref self: ContractState, x: u128) { + self.stored_data.write(x); + } + + fn get(self: @ContractState) -> u128 { + self.stored_data.read() + } } -} ``` -This contract defines `PAUSER_ROLE` and `MINTER_ROLE`. The `pause` and `unpause` functions are restricted to addresses with the `PAUSER_ROLE`, while `mint` is restricted to addresses with the `MINTER_ROLE`. The `before_update` hook ensures that token transfers are blocked when the contract is paused. The constructor grants all roles to the deployer. +**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. -Smart Contract Security Best Practices +## Private Functions -# Smart Contract Security Best Practices +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. + +```cairo,noplayground + #[generate_trait] + impl InternalFunctions of InternalFunctionsTrait { + fn _store_name(ref self: ContractState, user: ContractAddress, name: felt252) { + // ... implementation ... + } + } +``` -Developing secure smart contracts is crucial, as errors can lead to significant asset loss or functional failures. Smart contracts operate in a public environment, making them susceptible to exploitation by malicious actors. +## Constructors -## Mindset +Constructors, marked with `#[constructor]`, run only once during contract deployment to initialize the contract's state. A contract can have only one constructor. -Cairo is designed to be a safe language, encouraging developers to handle all possible cases. Security vulnerabilities in Starknet often arise from the design of smart contract flows rather than language-specific issues. Adopting a security-first mindset, considering all potential scenarios, is the initial step towards writing secure code. +```cairo,noplayground + #[constructor] + fn constructor(ref self: ContractState, owner: Person) { + self.names.entry(owner.address).write(owner.name); + self.total_names.write(1); + } +``` -### Viewing Smart Contracts as Finite State Machines +## Contract Storage -Smart contracts can be conceptualized as finite state machines. Each transaction represents a state transition. The constructor defines the initial states, and external functions facilitate transitions between these states. Transactions are atomic, succeeding or failing without partial changes. +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`. -### Input Validation +- **Accessing State**: Use `.read()` to retrieve a value and `.write()` to store or update a value. -The `assert!` and `panic!` macros are essential for validating conditions before executing actions. These validations can cover: +```cairo,noplayground + self.stored_data.read() + self.stored_data.write(x); +``` -- Caller-provided inputs. -- Execution prerequisites. -- Invariants (conditions that must always hold true). -- Return values from external function calls. +## Contract Events -For instance, `assert!` can verify sufficient funds before a withdrawal, preventing the transaction if the condition is not met. +Events inform the outside world about contract changes. They are defined in an enum annotated with `#[event]` and emitted using `self.emit()`. ```cairo,noplayground - impl Contract of IContract { - fn withdraw(ref self: ContractState, amount: u256) { - let current_balance = self.balance.read(); + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + BookAdded: BookAdded, + #[flat] + FieldUpdated: FieldUpdated, + BookRemoved: BookRemoved, + } +``` - assert!(self.balance.read() >= amount, "Insufficient funds"); +## Starknet Types - self.balance.write(current_balance - amount); - } -``` +Starknet provides specialized types for blockchain interactions: + +- `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. -These checks enforce constraints, clearly defining the boundaries for state transitions and ensuring the contract operates within expected limits. +## Contract Classes and Instances -## Recommendations +- **Contract Class**: The definition of a contract's code. +- **Contract Instance**: A deployed contract with its own storage. -### Checks-Effects-Interactions Pattern +## Contract Address Computation -This pattern, while primarily known for preventing reentrancy attacks on Ethereum, is also recommended for Starknet contracts. It dictates the order of operations within functions: +A contract address is computed using a hash of prefix, deployer address, salt, class hash, and constructor calldata hash. -1. **Checks**: Validate all conditions and inputs before any state modifications. -2. **Effects**: Perform all internal state changes. -3. **Interactions**: Execute external calls to other contracts last. +## Class Hash Computation -Testing Smart Contracts with Starknet Foundry +A class hash is the chain hash of its components: version, entry points, ABI hash, and Sierra program hash. -Introduction to Smart Contract Testing and Starknet Foundry +Starknet System Calls -### Introduction to Smart Contract Testing and Starknet Foundry +# Starknet System Calls -#### The Need for Smart Contract Testing +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: -Testing smart contracts is a critical part of the development process, ensuring they behave as expected and are secure. While the `scarb` command-line tool is useful for testing standalone Cairo programs and functions, it lacks the functionality required for testing smart contracts that necessitate control over the contract state and execution context. Therefore, Starknet Foundry, a smart contract development toolchain for Starknet, is introduced to address these needs. +- `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` -#### Example: PizzaFactory Contract +## `get_block_hash` -Throughout this chapter, the `PizzaFactory` contract serves as an example to demonstrate writing tests with Starknet Foundry. +### Syntax ```cairo,noplayground -use starknet::ContractAddress; +pub extern fn get_block_hash_syscall( + block_number: u64, +) -> SyscallResult implicits(GasBuiltin, System) nopanic; +``` -#[starknet::interface] -pub trait IPizzaFactory { - fn increase_pepperoni(ref self: TContractState, amount: u32); - fn increase_pineapple(ref self: TContractState, amount: u32); - fn get_owner(self: @TContractState) -> ContractAddress; - fn change_owner(ref self: ContractState, new_owner: ContractAddress); - fn make_pizza(ref self: ContractState); - fn count_pizza(self: @TContractState) -> u32; -} +### Description -#[starknet::contract] -pub mod PizzaFactory { - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address}; - use super::IPizzaFactory; +Retrieves the hash of a specific Starknet block within the range of `[first_v0_12_0_block, current_block - 10]`. - #[storage] - pub struct Storage { - pepperoni: u32, - pineapple: u32, - pub owner: ContractAddress, - pizzas: u32, - } +### Return Values - #[constructor] - fn constructor(ref self: ContractState, owner: ContractAddress) { - self.pepperoni.write(10); - self.pineapple.write(10); - self.owner.write(owner); - } +Returns the hash of the specified block. - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - PizzaEmission: PizzaEmission, - } +### Error Messages - #[derive(Drop, starknet::Event)] - pub struct PizzaEmission { - pub counter: u32, - } +- `Block number out of range`: `block_number` is greater than `current_block - 10`. +- `0`: `block_number` is less than the first block number of v0.12.0. - #[abi(embed_v0)] - impl PizzaFactoryimpl of super::IPizzaFactory { - fn increase_pepperoni(ref self: ContractState, amount: u32) { - assert!(amount != 0, "Amount cannot be 0"); - self.pepperoni.write(self.pepperoni.read() + amount); - } +## `get_execution_info` - fn increase_pineapple(ref self: ContractState, amount: u32) { - assert!(amount != 0, "Amount cannot be 0"); - self.pineapple.write(self.pineapple.read() + amount); - } +### Syntax - fn make_pizza(ref self: ContractState) { - assert!(self.pepperoni.read() > 0, "Not enough pepperoni"); - assert!(self.pineapple.read() > 0, "Not enough pineapple"); +```cairo,noplayground +pub extern fn get_execution_info_syscall() -> SyscallResult< + Box, +> implicits(GasBuiltin, System) nopanic; +``` - let caller: ContractAddress = get_caller_address(); - let owner: ContractAddress = self.get_owner(); +### Description - assert!(caller == owner, "Only the owner can make pizza"); +Fetches information about the original transaction. In Cairo 1.0, all block, transaction, and execution context getters are consolidated into this single system call. - self.pepperoni.write(self.pepperoni.read() - 1); - self.pineapple.write(self.pineapple.read() - 1); - self.pizzas.write(self.pizzas.read() + 1); +### Arguments - self.emit(PizzaEmission { counter: self.pizzas.read() }); - } +None. - fn get_owner(self: @ContractState) -> ContractAddress { - self.owner.read() - } +### Return Values - fn change_owner(ref self: ContractState, new_owner: ContractAddress) { - self.set_owner(new_owner); - } +Returns a struct containing the execution info. - fn count_pizza(self: @ContractState) -> u32 { - self.pizzas.read() - } - } +## `call_contract` - #[generate_trait] - pub impl InternalImpl of InternalTrait { - fn set_owner(ref self: ContractState, new_owner: ContractAddress) { - let caller: ContractAddress = get_caller_address(); - assert!(caller == self.get_owner(), "Only the owner can set ownership"); +### Syntax - self.owner.write(new_owner); - } - } -} +```cairo,noplayground +pub extern fn call_contract_syscall( + address: ContractAddress, entry_point_selector: felt252, calldata: Span, +) -> SyscallResult> implicits(GasBuiltin, System) nopanic; ``` -Project Setup and Contract Deployment with Starknet Foundry +### Description -# Project Setup and Contract Deployment with Starknet Foundry +Calls a specified contract with the given address, entry point selector, and call arguments. -## Configuring your Scarb project with Starknet Foundry +_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.* -To use Starknet Foundry as your testing tool, add it as a dev dependency in your `Scarb.toml` file. The `scarb test` command can be configured to execute `snforge test` by setting the `test` script in `Scarb.toml`. +### Arguments -```toml,noplayground -[dev-dependencies] -snforge_std = "0.39.0" +- `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. -[scripts] -test = "snforge test" +### Return Values -[tool.scarb] -allow-prebuilt-plugins = ["snforge_std"] -``` +The call response, of type `SyscallResult>`. -After configuring your project, install Starknet Foundry following the official documentation. +## `deploy` -## Testing Smart Contracts with Starknet Foundry +### Syntax -The `scarb test` command, when configured as above, will execute `snforge test`. The typical testing flow for a contract involves: +```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; +``` -1. Declaring the contract's class. -2. Serializing the constructor calldata. -3. Deploying the contract and obtaining its address. -4. Interacting with the contract's entrypoints to test scenarios. +### Description -### Deploying the Contract to Test +Deploys a new instance of a previously declared class. -Testing Smart Contract State, Functions, and Events +### Arguments -# Testing Smart Contract State, Functions, and Events +- `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. -When testing smart contracts with Starknet Foundry, it's essential to verify their state, functions, and events. This involves deploying the contract, interacting with its functions, and asserting expected outcomes. +### Return Values -### Testing Contract State +A tuple containing the deployed contract's address and the constructor's response array. -To test the initial state of a contract, you can use the `load` function from `snforge_std` to read storage variables directly. This is useful even if these variables are not exposed through public entrypoints. +## `emit_event` -```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_constructor() { - let (pizza_factory, pizza_factory_address) = deploy_pizza_factory(); +### Syntax - 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()); -} +```cairo,noplayground +pub extern fn emit_event_syscall( + keys: Span, data: Span, +) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; ``` -### Testing Contract Functions and Ownership +### Description -To test functions that have access control, such as changing ownership, you can use `start_cheat_caller_address` to mock the caller's address. This allows you to simulate calls made by the owner and by unauthorized users, asserting the expected behavior (e.g., successful owner change or a panic for unauthorized attempts). +Emits an event with specified keys and data. Keys are analogous to Ethereum event topics, and data contains the event payload. -```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(); +### Arguments - let new_owner: ContractAddress = contract_address_const::<'new_owner'>(); - assert_eq!(pizza_factory.get_owner(), owner()); +- `keys`: The event's keys. +- `data`: The event's data. - start_cheat_caller_address(pizza_factory_address, owner()); +### Return Values - pizza_factory.change_owner(new_owner); +None. - assert_eq!(pizza_factory.get_owner(), new_owner); -} +### Example -#[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); -} +```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(); ``` -### Testing Emitted Events +## `library_call` -To verify that events are emitted correctly, you can use the `spy_events` function. This function captures emitted events, allowing you to assert that they were emitted with the expected parameters. This is often combined with testing function logic, such as incrementing a counter when a pizza is made. +### Syntax ```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(); -# } -# -#[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(); +pub extern fn library_call_syscall( + class_hash: ClassHash, function_selector: felt252, calldata: Span, +) -> SyscallResult> implicits(GasBuiltin, System) nopanic; +``` - // When - pizza_factory.make_pizza(); +### Description - // 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)]); -} +Executes the logic of another class within the context of the caller. Requires the class hash, function selector, and serialized calldata. + +## `get_class_hash_at` + +### Syntax + +```cairo,noplayground +pub extern fn get_class_hash_at_syscall( + contract_address: ContractAddress, +) -> SyscallResult implicits(GasBuiltin, System) nopanic; ``` -Unit Testing Internal Contract Logic +### Description + +Retrieves the class hash of the contract at the given address. -# Unit Testing Internal Contract Logic +### Arguments -Starknet Foundry provides a way to test the internal logic of a contract without deploying it by using the `contract_state_for_testing` function. This function creates an instance of the `ContractState` struct, which contains zero-sized fields corresponding to the contract's storage variables. This allows direct access and modification of these variables. +- `contract_address`: The address of the deployed contract. -To use this functionality, you need to manually import the traits that define access to the storage variables. Once these imports are in place, you can interact with the contract's internal functions directly. +### Return Values -For example, to test the `set_owner` function and read the `owner` storage variable: +The class hash of the contract's originating code. -```cairo -use crate::pizza::PizzaFactory::{InternalTrait}; -use crate::pizza::{IPizzaFactoryDispatcher, IPizzaFactoryDispatcherTrait, PizzaFactory}; -use starknet::{ContractAddress, contract_address_const}; +## `replace_class` -#[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); -} +### Syntax + +```cairo,noplayground +pub extern fn replace_class_syscall( + class_hash: ClassHash, +) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; ``` -This approach is mutually exclusive with deploying the contract. If you deploy the contract, you interact via the dispatcher; if you test internal functions, you interact directly with the `ContractState` object. +### Description -Running tests with `scarb test` will show results for tests that use this method, such as `test_set_as_new_owner_direct`. +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. -Static Analysis and Functional Language Features in Testing +### Arguments -# Static Analysis and Functional Language Features in Testing +- `class_hash`: The hash of the class to use as a replacement. -No content available for this section. +### Return Values -Closures in Cairo +None. -What are Closures in Cairo? +## `storage_read` -# Closures in Cairo +### Syntax -## What are Closures in Cairo? +```cairo,noplayground +pub extern fn storage_read_syscall( + address_domain: u32, address: StorageAddress, +) -> SyscallResult implicits(GasBuiltin, System) nopanic; +``` -Closures are anonymous functions that can be stored in variables or passed as arguments to other functions. They allow for code reuse and behavior customization by capturing values from their defining scope. This makes them particularly useful for passing behavior as a parameter to other functions, especially when working with collections, error handling, or customizing function behavior. +### Description -> Note: Closures were introduced in Cairo 2.9 and are still under development. Future versions will introduce more features. +Reads a value from the contract's storage at the specified address domain and storage address. -Defining and Using Closures +### Return Values -# Defining and Using Closures +The value read from storage as a `felt252`. -Closures in Cairo are anonymous functions that can capture values from their enclosing scope. They are defined using the `|parameters| body` syntax. +Contract Storage Management -## Closure Syntax and Type Inference +# Contract Storage Management -The syntax for closures is similar to functions, with parameters enclosed in pipes (`|`) and the body following. Type annotations for parameters and return values are optional, as the compiler can infer them from the context. +Starknet contracts manage their state through contract storage, which can be accessed in two primary ways: -```cairo -// Function definition for comparison -fn add_one_v1 (x: u32) -> u32 { x + 1 } +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. -// Fully annotated closure -let add_one_v2 = |x: u32| -> u32 { x + 1 }; +## Declaring and Using Storage Variables -// Closure with inferred types -let add_one_v3 = |x| { x + 1 }; -let add_one_v4 = |x| x + 1; // Brackets optional for single-expression bodies +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. + +```cairo +#[storage] +struct Storage { + owner: Person, + expiration: Expiration, +} ``` -When types are not explicitly annotated, Cairo infers them based on usage. If the compiler cannot infer types, it may require explicit annotations or values to be provided. +### Accessing Storage Variables -## Example Usage +Automatically generated `read` and `write` functions are available for each storage variable. For compound types like structs, you can access individual members directly. -Closures can be used for concise inline logic, especially with collection methods. +To read a variable: ```cairo -// Example of a closure capturing a variable from its environment -let x = 8; -let my_closure = |value| { - x * (value + 3) -}; -println!("my_closure(1) = {}", my_closure(1)); // Output: my_closure(1) = 32 - -// Using closures with array methods -let numbers = array![1, 2, 3]; -let doubled = numbers.map(|item: u32| item * 2); -println!("doubled: {:?}", doubled); // Output: doubled: [2, 4, 6] +let owner_data = self.owner.read(); +``` -let squared = numbers.map(|item: u32| { - let x: u64 = item.into(); - x * x -}); -println!("squared: {:?}", squared); // Output: squared: [1, 4, 9] +To write a variable: -let even_numbers = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); -println!("even_numbers: {:?}", even_numbers); // Output: even_numbers: [4, 6] +```cairo +self.owner.write(new_owner_data); +``` -// Example with multiple parameters and type inference -let sum = |x: u32, y: u32, z: u16| { - x + y + z.into() -}; -println!("Sum result: {}", sum(1, 2, 3)); // Output: Sum result: 6 +When working with struct members, you can read/write them directly: -// Note: Type inference can be strict. -let double = |value| value * 2; -println!("Double of 2 is {}", double(2_u8)); // Inferred as u8 -// println!("Double of 6 is {}", double(6_u16)); // This would fail as type is inferred as u8 +```cairo +// Reading a specific member of a struct +fn get_owner_name(self: @ContractState) -> felt252 { + self.owner.name.read() +} ``` -Closure Type Inference and Traits +## Storing Custom Types with the `Store` Trait + +To store custom types (structs, enums) in storage, they must implement the `Store` trait. This can be achieved by deriving it: -# Closure Type Inference and Annotation +```cairo +#[derive(Drop, Serde, starknet::Store)] +pub struct Person { + address: ContractAddress, + name: felt252, +} +``` -Closures in Cairo generally do not require explicit type annotations for their parameters or return values, unlike `fn` functions. This is because closures are typically used in narrow, internal contexts rather than as part of a public interface. The compiler can infer these types, similar to how it infers variable types, though annotations can be added for explicitness. +Enums used in storage must also implement `Store` and define a default variant using `#[default]`: ```cairo -# fn generate_workout(intensity: u32, random_number: u32) { - let expensive_closure = |num: u32| -> u32 { - num - }; -# -# if intensity < 25 { -# println!("Today, do {} pushups!", expensive_closure(intensity)); -# println!("Next, do {} situps!", expensive_closure(intensity)); -# } else { -# if random_number == 3 { -# println!("Take a break today! Remember to stay hydrated!"); -# } else { -# println!("Today, run for {} minutes!", expensive_closure(intensity)); -# } -# } -# } -# -# #[executable] -# fn main() { -# let simulated_user_specified_value = 10; -# let simulated_random_number = 7; -# -# generate_workout(simulated_user_specified_value, simulated_random_number); -# } +#[derive(Copy, Drop, Serde, starknet::Store)] +pub enum Expiration { + Finite: u64, + #[default] + Infinite, +} ``` -If a closure's types are inferred, they are locked in by the first call. Attempting to call it with a different type will result in a compile-time error. +Both `Drop` and `Serde` are required for proper serialization/deserialization. -```cairo, noplayground -# //TAG: does_not_compile -# #[executable] -# fn main() { - let example_closure = |x| x; +### Structs Storage Layout - let s = example_closure(5_u64); - let n = example_closure(5_u32); -# } -``` +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. -The compiler error arises because the closure `example_closure` is first called with a `u64`, inferring `x` and the return type as `u64`. A subsequent call with `u32` violates this inferred type. +| Fields | Address | +| --------- | --------------------------- | +| `owner` | `owner.__base_address__` | +| `address` | `owner.__base_address__ +1` | -## Closure Traits (`FnOnce`, `Fn`, `FnMut`) +### Enums Storage Layout -Closures implement traits based on how they handle captured environment values: +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. -1. **`FnOnce`**: Implemented by closures that can be called once. This includes closures that move captured values out of their body. All closures implement `FnOnce`. -2. **`Fn`**: Implemented by closures that do not move or mutate captured values, or capture nothing. These can be called multiple times without affecting their environment. -3. **`FnMut`**: Implemented by closures that might mutate captured values but do not move them out of their body. These can also be called multiple times. +For `Expiration`: -The `unwrap_or_else` method on `OptionTrait` demonstrates the use of `FnOnce`: +- `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__` | -```cairo, ignore -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(), - } - } +## Storage Nodes + +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. + +```cairo +#[starknet::storage_node] +struct ProposalNode { + title: felt252, + description: felt252, + yes_votes: u32, + no_votes: u32, + voters: Map, } ``` -The trait bound `impl func: core::ops::FnOnce[Output: T]` signifies that the closure `f` is called at most once, which aligns with the `unwrap_or_else` logic where `f` is only executed if the `Option` is `None`. +Accessing members of a storage node involves navigating through its fields: + +```cairo +let mut proposal = self.proposals.entry(proposal_id); +proposal.title.write(title); +``` -Closures for Array Transformations +## Low-Level Storage Access (Syscalls) -# Closures for Array Transformations +Direct access to storage can be done via system calls: -Closures are essential for implementing functional programming patterns like `map` and `filter` for arrays. They can be passed as arguments to these functions, allowing for flexible data transformations. +### `storage_read_syscall` -## `map` Operation +Reads a value from a specified storage address. -The `map` function applies a given closure to each element of an array, producing a new array with the results. The element type of the output array is determined by the return type of the closure. +- **Arguments**: `address_domain` (currently `0` for onchain mode), `address` (the storage address). +- **Return**: `SyscallResult`. ```cairo -#[generate_trait] -impl ArrayExt of ArrayExtTrait { - // Needed in Cairo 2.11.4 because of a bug in inlining analysis. - #[inline(never)] - fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( - self: Array, f: F, - ) -> Array { - let mut output: Array = array![]; - for elem in self { - output.append(f(elem)); - } - output - } -} +use starknet::storage_access::storage_base_address_from_felt252; + +let storage_address = storage_base_address_from_felt252(3534535754756246375475423547453); +storage_read_syscall(0, storage_address).unwrap_syscall(); ``` -Example usage: +### `storage_write_syscall` -```cairo - 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 - }); +Writes a value to a specified storage address. + +- **Arguments**: `address_domain` (currently `0`), `address` (storage address), `value` (`felt252`). +- **Return**: `SyscallResult<()>`. - println!("double: {:?}" , double); - println!("another: {:?}" , another); +```cairo +pub extern fn storage_write_syscall( + address_domain: u32, address: StorageAddress, value: felt252, +) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; ``` -## `filter` Operation +## Storing Key-Value Pairs with Mappings + +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. -The `filter` function creates a new array containing only the elements from the original array for which the provided closure returns `true`. The closure must have a return type of `bool`. +To declare a mapping: ```cairo -#[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 - } -} +user_values: Map, ``` -Example usage: +To access or modify entries: ```cairo - let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); - println!("even: {:?}" , even); +// Writing to a mapping +self.user_values.entry(caller).write(amount); + +// Reading from a mapping +let value = self.user_values.entry(address).read(); ``` -Closure Behavior and Limitations +Nested mappings are supported: -# Closure Behavior and Limitations +```cairo +user_warehouse: Map>, +``` -Closures in Cairo can capture bindings from their enclosing scope. This means a closure can access and use variables defined outside of its own body. +Access requires chaining `entry` calls: `self.user_warehouse.entry(caller).entry(item_id).write(quantity);` -For instance, the following closure `my_closure` uses the binding `x` from its surrounding environment to compute its result: +## Storing Ordered Collections with Vectors -```cairo -# #[generate_trait] -# impl ArrayExt of ArrayExtTrait { -# // Needed in Cairo 2.11.4 because of a bug in inlining analysis. -# #[inline(never)] -# fn map, F, +Drop, impl func: core::ops::Fn, +Drop>( -# self: Array, f: F, -# ) -> Array { -# let mut output: Array = array![]; -# for elem in self { -# output.append(f(elem)); -# } -# output -# } -# } -# -# #[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 -# } -# } -# -# #[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) - }; +Vectors (`Vec`) store ordered collections in storage. Unlike memory arrays (`Array`), `Vec` is a phantom type for storage. - println!("my_closure(1) = {}", my_closure(1)); -# -# 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 -# }); -# -# println!("double: {:?}", double); -# println!("another: {:?}", another); -# -# let even = array![3, 4, 5, 6].filter(|item: u32| item % 2 == 0); -# println!("even: {:?}", even); -# } +To declare a vector: + +```cairo +addresses: Vec, ``` -The arguments for a closure are placed between pipes (`|`). Type annotations for arguments and return values are generally inferred from usage. If a closure is used with inconsistent types, a `Type annotations needed` error will occur, prompting the user to specify the types. The closure body can be a single expression without braces `{}` or a multi-line expression enclosed in braces `{}`. +Operations include: -Custom Data Structures in Cairo +- `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). -Custom Data Structures and Type Conversions +```cairo +// Appending to a vector +self.addresses.push(caller); -# Custom Data Structures and Type Conversions +// Getting an element by index +self.addresses.get(index).map(|ptr| ptr.read()); -Cairo allows the definition of custom data structures using `structs`. These structures can hold fields of various types, including other custom types. +// Modifying an element +self.addresses[index].write(new_address); +``` -## Conversions of Custom Types +To retrieve all elements, iterate and append to a memory `Array`. -Cairo supports defining type conversions for custom types, similar to built-in types. +## Addresses of Storage Variables -### `Into` Trait +Storage variable addresses are computed using `sn_keccak` hashes of variable names. -The `Into` trait enables defining conversions where the compiler can infer the target type. This typically requires explicitly stating the target type during conversion. +- **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. -```cairo -#[derive(Drop, PartialEq)] -struct Rectangle { - width: u64, - height: u64, -} +The base address of a storage variable can be accessed via `__base_address__`. -#[derive(Drop)] -struct Square { - side_length: u64, -} +## Optimizing Storage Costs with Bit-packing -impl SquareIntoRectangle of Into { - fn into(self: Square) -> Rectangle { - Rectangle { width: self.side_length, height: self.side_length } - } -} +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. -#[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!", - ); +For example, packing `u8`, `u32`, and `u64` (total 104 bits) into a single `u128` slot: + +```cairo +struct Sizes { + tiny: u8, + small: u32, + medium: u64, } ``` -### `TryInto` Trait +The storage slot would store these packed values, requiring bitwise operations for access. -The `TryInto` trait allows for fallible conversions, returning an `Option` or `Result`. +Contract Interaction and Communication -```cairo -#[derive(Drop)] -struct Rectangle { - width: u64, - height: u64, -} +# Contract Interaction and Communication -#[derive(Drop, PartialEq)] -struct Square { - side_length: u64, -} +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. -impl RectangleIntoSquare of TryInto { - fn try_into(self: Rectangle) -> Option { - if self.height == self.width { - Some(Square { side_length: self.height }) - } else { - None - } - } -} +## Contract Class ABI -#[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.", - ); +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. - 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.", - ); -} -``` +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. + +### Encoding and Decoding + +At the blockchain's low-level CASM instruction level, data is represented as `felt252`. The ABI dictates how higher-level types are serialized into `felt252` sequences for contract calls and deserialized back upon receiving data. + +## The Dispatcher Pattern + +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. + +The compiler automatically generates dispatchers for defined interfaces. For example, an `IERC20` interface might yield `IERC20Dispatcher` and `IERC20SafeDispatcher`. + +- **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. -## Handling Custom Data Structures with `Felt252Dict` +Under the hood, dispatchers utilize system calls like `starknet::syscalls::call_contract_syscall`. -When a struct contains a `Felt252Dict` member, it requires manual implementation of the `Destruct` trait to manage the dictionary's lifecycle. This is because `Felt252Dict` cannot be automatically dropped. +### Contract Dispatcher Example + +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. ```cairo -// Example of Destruct implementation for MemoryVec -// (Full MemoryVec implementation omitted for brevity) -struct MemoryVec { - data: Felt252Dict>, - len: usize, -} +#[starknet::contract] +mod TokenWrapper { + use starknet::{ContractAddress, get_caller_address}; + use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere + use super::{IERC20Dispatcher, IERC20DispatcherTrait}; -impl> Destruct> of Destruct> { - fn destruct(self: MemoryVec) nopanic { - self.data.squash(); + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl TokenWrapper of ITokenWrapper { + fn token_name(self: @ContractState, contract_address: ContractAddress) -> felt252 { + IERC20Dispatcher { contract_address }.name() + } + + fn transfer_token( + ref self: ContractState, + address: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) -> bool { + let erc20_dispatcher = IERC20Dispatcher { contract_address: address }; + erc20_dispatcher.transfer_from(get_caller_address(), recipient, amount) + } } } ``` -## Formatting Custom Types with `Display` +### Handling Errors with Safe Dispatchers -To format custom types for user consumption, the `Display` trait must be implemented. This trait provides a `fmt` method that defines how the struct's data should be represented as a string. +Safe dispatchers return a `Result>`, where `T` is the expected return type and `Array` contains the panic reason if the call fails. This allows for custom error handling logic. ```cairo -use core::fmt::{Display, Error, Formatter}; - -#[derive(Copy, Drop)] -struct Point { - x: u8, - y: u8, -} +#[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 -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(()) + // 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 + }, } } - -#[executable] -fn main() { - let p = Point { x: 1, y: 3 }; - println!("{} {}", p.x, p.y); // Expected output: Point (1, 3) -} ``` -The `write!` and `writeln!` macros can also be used with a `Formatter` to write formatted strings. +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. + +## Calling Contracts using Low-Level Calls -## Deref Coercion +Directly using `starknet::syscalls::call_contract_syscall` offers more control over data serialization and error handling than the dispatcher pattern. -The `Deref` trait allows types to be treated as references to another type. This enables accessing the fields of a wrapped type directly through the wrapper. +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. ```cairo -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} +#[starknet::contract] +mod TokenWrapper { + use starknet::{ContractAddress, SyscallResultTrait, get_caller_address, syscalls}; + use super::ITokenWrapper; // Assuming ITokenWrapper is defined elsewhere -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} + #[storage] + struct Storage {} -impl DerefWrapper of Deref> { - type Target = T; - fn deref(self: Wrapper) -> T { - self.value - } -} + 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); -#[executable] -fn main() { - let wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; - // Access fields directly via deref coercion - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); + let mut res = syscalls::call_contract_syscall( + address, selector!("transfer_from"), call_data.span(), + ) + .unwrap_syscall(); + + Serde::::deserialize(ref res).unwrap() + } + } } ``` -### Restricting Deref Coercion to Mutable Variables - -The `DerefMut` trait, when implemented, only applies to mutable variables. However, it does not inherently provide mutable access to the underlying data. - -Mutable Data Structures: Dictionaries and Dynamic Arrays +## Executing Code from Another Class (Library Calls) -# Mutable Data Structures: Dictionaries and Dynamic Arrays +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. -Cairo's standard arrays (`Array`) are immutable, meaning elements cannot be modified after insertion. This limitation is problematic for mutable data structures. For instance, updating an element at a specific index or removing an element from an array is not directly supported. +When Contract A uses a library call to execute logic from Class B: -```cairo,noplayground - let mut level_players = array![5, 1, 10]; -``` +- `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. -To overcome this, Cairo provides a built-in dictionary type, `Felt252Dict`, which can simulate mutable data structures. Dictionaries can be members of structs, allowing for more complex data management. +### Library Dispatchers -## Dictionaries as Struct Members +Similar to contract dispatchers, library dispatchers wrap a `ClassHash` and use `starknet::syscalls::library_call_syscall`. -A `Felt252Dict` can be included as a member within a struct to manage collections of data that require modification. For example, a user database could be implemented using a struct containing a dictionary to store user balances. +```cairo +// Assuming an interface IERC20 is defined +trait IERC20DispatcherTrait { + fn name(self: T) -> felt252; + fn transfer(self: T, recipient: ContractAddress, amount: u256); +} -```cairo,noplayground -struct UserDatabase { - users_updates: u64, - balances: Felt252Dict, +#[derive(Copy, Drop, starknet::Store, Serde)] +struct IERC20LibraryDispatcher { + class_hash: starknet::ClassHash, } -trait UserDatabaseTrait { - fn new() -> UserDatabase; - fn update_user<+Drop>(ref self: UserDatabase, name: felt252, balance: T); - fn get_balance<+Copy>(ref self: UserDatabase, name: felt252) -> T; +impl IERC20LibraryDispatcherImpl of IERC20DispatcherTrait { + fn name(self: IERC20LibraryDispatcher) -> felt252 { + // starknet::syscalls::library_call_syscall is called here + // ... implementation details ... + unimplemented!() + } + fn transfer(self: IERC20LibraryDispatcher, recipient: ContractAddress, amount: u256) { + // starknet::syscalls::library_call_syscall is called here + // ... implementation details ... + unimplemented!() + } } ``` -## Simulating a Dynamic Array with Dictionaries +### Using the Library Dispatcher -A dynamic array should support operations such as appending items, accessing items by index, setting values at specific indices, and returning the current length. This behavior can be defined using a trait. +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. -```cairo,noplayground -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; +```cairo +#[starknet::interface] +trait IValueStore { + fn set_value(ref self: TContractState, value: u128); + fn get_value(self: @TContractState) -> u128; } -``` -The core library includes a `Vec` for storage, but a custom implementation like `MemoryVec` can be created using `Felt252Dict` for mutability. +#[starknet::contract] +mod ValueStoreExecutor { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ClassHash, ContractAddress}; + use super::{IValueStoreDispatcherTrait, IValueStoreLibraryDispatcher}; -### Implementing a Dynamic Array in Cairo + #[storage] + struct Storage { + logic_library: ClassHash, + value: u128, // This contract's storage + } -Our `MemoryVec` struct uses a `Felt252Dict>` to store data, mapping indices (felts) to values, and a `len` field to track the number of elements. + #[constructor] + fn constructor(ref self: ContractState, logic_library: ClassHash) { + self.logic_library.write(logic_library); + } -```cairo,noplayground -# -# 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, + #[abi(embed_v0)] + impl ValueStoreExecutor of super::IValueStore { + fn set_value(ref self: ContractState, value: u128) { + // Calls set_value in ValueStoreLogic's class context, updating self.value + IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() } + .set_value(value); + } + + fn get_value(self: @ContractState) -> u128 { + // Calls get_value in ValueStoreLogic's class context, reading self.value + IValueStoreLibraryDispatcher { class_hash: self.logic_library.read() }.get_value() + } + } + + #[external(v0)] + fn get_value_local(self: @ContractState) -> u128 { + self.value.read() // Reads the local storage directly + } } ``` -The implementation of the `MemoryVecTrait` methods is as follows: +### Calling Classes using Low-Level Calls -```cairo,noplayground -# -# 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(); -# } -# } -# -impl MemoryVecImpl, +Copy> of MemoryVecTrait, T> { - fn new() -> MemoryVec { - MemoryVec { data: Default::default(), len: 0 } +The `starknet::syscalls::library_call_syscall` can be used directly for more granular control. It requires the class hash, function selector, and serialized arguments. + +```cairo +#[starknet::contract] +mod ValueStore { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ClassHash, SyscallResultTrait, syscalls}; + + #[storage] + struct Storage { + logic_library: ClassHash, + value: u128, + } + + #[constructor] + fn constructor(ref self: ContractState, logic_library: ClassHash) { + self.logic_library.write(logic_library); } - fn get(ref self: MemoryVec, index: usize) -> Option { - if index < self.len() { - Some(self.data.get(index.into()).deref()) - } else { - None - } - } + #[external(v0)] + fn set_value(ref self: ContractState, value: u128) -> bool { + let mut call_data: Array = array![]; + Serde::serialize(@value, ref call_data); + + let mut res = syscalls::library_call_syscall( + self.logic_library.read(), selector!("set_value"), call_data.span(), + ) + .unwrap_syscall(); - fn at(ref self: MemoryVec, index: usize) -> T { - assert!(index < self.len(), "Index out of bounds"); - self.data.get(index.into()).deref() + Serde::::deserialize(ref res).unwrap() } - 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 + #[external(v0)] + fn get_value(self: @ContractState) -> u128 { + self.value.read() } } ``` -This implementation allows for dynamic array-like behavior by leveraging the mutability of `Felt252Dict`. - -Stack Data Structure Implementation - -# Stack Data Structure Implementation - -A Stack is a LIFO (Last-In, First-Out) collection where elements are added and removed from the same end, known as the top. +Starknet Components -## Stack Operations Interface +# Starknet Components -The necessary operations for a stack are: +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. -- Push an item to the top of the stack. -- Pop an item from the top of the stack. -- Check if the stack is empty. +## What's in a Component? -This can be defined by the following trait: +Similar to contracts, components can contain: -```cairo,noplayground -trait StackTrait { - fn push(ref self: S, value: T); - fn pop(ref self: S) -> Option; - fn is_empty(self: @S) -> bool; -} -``` +- Storage variables +- Events +- External and internal functions -## Mutable Stack Implementation in Cairo +## Creating Components -A stack can be implemented in Cairo using a `Felt252Dict` to store the stack elements and a `usize` field to track the stack's length. +To create a component: -The `NullableStack` struct is defined as: +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. -```cairo,noplayground -struct NullableStack { - data: Felt252Dict>, - len: usize, +```cairo +// Example of an embeddable impl for a component +#[embeddable_as(name)] +impl ComponentName< + TContractState, +HasComponent, +> of super::InterfaceName> { + // Component functions implementation } ``` -### Implementing `push` and `pop` - -The `push` function inserts an element at the index indicated by `len` and increments `len`. The `pop` function decrements `len` and then retrieves the element at the new `len` index. - -```cairo,noplayground -# -# 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, -# } -# -# impl DestructNullableStack> of Destruct> { -# fn destruct(self: NullableStack) nopanic { -# self.data.squash(); -# } -# } -# -# -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; - } +Functions within these `impl` blocks expect arguments like `ref self: ComponentState` (for state-modifying functions) or `self: @ComponentState` (for view functions). This makes the `impl` generic over `TContractState`, allowing its use in any contract that implements the `HasComponent` trait. - fn pop(ref self: NullableStack) -> Option { - if self.is_empty() { - return None; - } - self.len -= 1; - Some(self.data.get(self.len.into()).deref()) - } +Internal functions can be defined in a separate `impl` block without the `#[embeddable_as]` attribute; they are not exposed externally but can be used within the embedding contract. - fn is_empty(self: @NullableStack) -> bool { - *self.len == 0 - } -} -``` +## Migrating a Contract to a Component -The full implementation, along with other data structures, is available in the Alexandria library. +To migrate a contract to a component, make the following changes: -Recursive Data Structures +- 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`. -# Recursive Data Structures +## Using Components Inside a Contract -Recursive data structures allow a value of a type to contain another value of the same type. This poses a compile-time challenge because Cairo needs to know the exact size of a type, and infinite nesting could make this impossible. To address this, a `Box` can be used within the recursive type definition, as `Box` has a known size (it's a pointer). +To integrate a component into a contract: -## Binary Tree Example +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)]`. -A binary tree is a data structure where each node has at most two children: a left child and a right child. A leaf node has no children. +```cairo +#[starknet::contract] +mod MyContract { + // Declare the component + component!(path: MyComponent, storage: my_comp_storage, event: MyComponentEvent); -### Initial Attempt (Fails Compilation) + // Embed the component's logic + #[abi(embed_v0)] + impl MyComponentImpl = MyComponent::MyComponentImpl; -An initial attempt to define a binary tree might look like this: + #[storage] + struct Storage { + // Contract's own storage + my_data: u128, + // Component's storage, annotated with substorage + #[substorage(v0)] + my_comp_storage: MyComponent::Storage, + } -```cairo, noplayground -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, BinaryTree, BinaryTree), -} + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + // Contract's own events + MyDataUpdated: MyDataUpdated, + // Component's events + MyComponentEvent: MyComponent::Event, + } -#[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)); + // ... rest of the contract } ``` -This code fails because the `BinaryTree` type, as defined, does not have a known size due to the direct nesting of `BinaryTree` within `Node`. +The component's functions can then be called externally using a dispatcher instantiated with the contract's address. -### Solution with `Box` +## Component Dependencies -To make the recursive type compilable, `Box` is used to store the recursive variants. `Box` is a pointer, and its size is constant regardless of the data it points to. This breaks the infinite recursion chain, allowing the compiler to determine the type's size. +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. -The corrected `BinaryTree` definition and usage are as follows: +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. ```cairo -mod display; -use display::DebugBinaryTree; - -#[derive(Copy, Drop)] -enum BinaryTree { - Leaf: u32, - Node: (u32, Box, Box), -} +// 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 + #[storage] + pub struct Storage { + value: u32, + } -#[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))); + #[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() + } - println!("{:?}", root); + 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); + } + } } ``` -In this version, the `Node` variant contains `(u32, Box, Box)`. This means a `Node` stores a `u32` and two pointers to `BinaryTree` values, which are stored separately. This approach ensures that the `Node` variant has a known size, enabling the `BinaryTree` type to compile. - -Smart Pointers in Cairo +## Example: An Ownable Component -Introduction to Cairo's Memory Model and Smart Pointers +This example demonstrates an `Ownable` component that manages contract ownership. -# Introduction to Cairo's Memory Model and Smart Pointers +### Interface (`IOwnable`) -A pointer is a variable that contains a memory address, pointing to other data. Pointers can lead to bugs and security vulnerabilities, such as referencing unassigned memory, causing crashes. To prevent these issues, Cairo employs Smart Pointers. - -Smart pointers are data structures that behave like pointers but include additional metadata and capabilities. Originating in C++ and also present in languages like Rust, smart pointers in Cairo ensure memory is accessed safely and provably by enforcing strict type checking and ownership rules, thus preventing unsafe memory addressing that could compromise a program's provability. - -Understanding `Box` in Cairo - -# Understanding `Box` in Cairo +```cairo +#[starknet::interface] +trait IOwnable { + fn owner(self: @TContractState) -> ContractAddress; + fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TContractState); +} +``` -## What is `Box`? +### Component Implementation (`OwnableComponent`) -The principal smart pointer type in Cairo is `Box`. It allows you to store data in a specific memory segment called the "boxed segment." When you create a `Box`, the data of type `T` is appended to this segment, and the execution segment holds only a pointer to the boxed data. +```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; -## When to Use `Box` + #[storage] + pub struct Storage { + owner: ContractAddress, + } -You will typically use `Box` in the following situations: + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + OwnershipTransferred: OwnershipTransferred, + } -- **Unknown Compile-Time Size:** When you have a type whose size cannot be determined at compile time, and you need to use a value of that type in a context requiring a fixed size. -- **Efficient Large Data Transfer:** When you need to transfer ownership of a large amount of data and want to ensure it is not copied during the transfer. + #[derive(Drop, starknet::Event)] + struct OwnershipTransferred { + previous_owner: ContractAddress, + new_owner: ContractAddress, + } -Performance and Recursive Types with `Box` + // Embeddable implementation of the interface + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent, + > of super::IOwnable> { + fn owner(self: @ComponentState) -> ContractAddress { + self.owner.read() + } -# Performance and Recursive Types with `Box` + 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); + } -Storing large amounts of data directly can be slow due to memory copying. Using `Box` improves performance by storing the data in the boxed segment, allowing only a small pointer to be copied. + fn renounce_ownership(ref self: ComponentState) { + self.assert_only_owner(); + self._transfer_ownership(Zero::zero()); + } + } -### Using a `Box` to Store Data in the Boxed Segment + // Internal functions implementation + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn initializer(ref self: ComponentState, owner: ContractAddress) { + self._transfer_ownership(owner); + } -A `Box` can be used to store data in the boxed segment. This is useful for optimizing the transfer of large 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 + } -```cairo -#[executable] -fn main() { - let b = BoxTrait::new(5_u128); - println!("b = {}", b.unbox()) + 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 }, + ); + } + } } ``` -This code snippet demonstrates storing a `u128` value in the boxed segment using `BoxTrait::new()`. While storing a single value in a box isn't common, it illustrates the mechanism. - -### Enabling Recursive Types with Boxes +## Components Under the Hood -Boxes are essential for defining recursive types, which would otherwise be disallowed due to their potentially infinite size. +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. -The `Deref` Trait and Deref Coercion +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`. -# The `Deref` Trait and Deref Coercion +## Stacking Components -The `Deref` trait allows a type to be treated like a reference to another type. This enables deref coercion, which permits accessing the members of a wrapped type directly through the wrapper itself. +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. -## Practical Example: `Wrapper` +Contract Upgradeability -Consider a generic wrapper type `Wrapper` designed to wrap another type `T`. +# Contract Upgradeability -```cairo, noplayground -#[derive(Drop, Copy)] -struct UserProfile { - username: felt252, - email: felt252, - age: u16, -} +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. -#[derive(Drop, Copy)] -struct Wrapper { - value: T, -} -``` +## How Upgradeability Works in Starknet -To facilitate access to the wrapped value, the `Deref` trait is implemented for `Wrapper`: +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. -```cairo, noplayground -impl DerefWrapper of Deref> { - type Target = T; - fn deref(self: Wrapper) -> T { - self.value - } -} -``` +## Replacing Contract Classes -This implementation of `deref` simply returns the wrapped value. As a result, instances of `Wrapper` can directly access the members of the inner type `T` through deref coercion. +### The `replace_class_syscall` -For instance, when `Wrapper` is used, its fields can be accessed as if they were directly on `UserProfile`: +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. -```cairo, noplayground -#[executable] -fn main() { - let wrapped_profile = Wrapper { - value: UserProfile { username: 'john_doe', email: 'john@example.com', age: 30 }, - }; +```cairo +use core::num::traits::Zero; +use starknet::{ClassHash, syscalls}; - // Access fields directly via deref coercion - println!("Username: {}", wrapped_profile.username); - println!("Current age: {}", wrapped_profile.age); +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 Pointers: Quiz and Key Concepts - -# Smart Pointers: Quiz and Key Concepts +Listing: Exposing `replace_class_syscall` to update the contract's class -## Key Concepts of Smart Pointers +If a contract is deployed without a dedicated upgrade mechanism, its class hash can still be replaced using library calls. -Smart pointers in Cairo offer capabilities beyond simple references, including memory management, strict type checking, and ownership rules to ensure memory safety. They prevent issues like null dereferences and access to uninitialized memory. Examples include `Box` and `Nullable`. +### Class Hash Management Example -## Quiz Insights +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. -- **What smart pointers are NOT:** Smart pointers are _not_ types that store a reference to a value without providing automatic memory management or ownership tracking. They actively help prevent memory issues and enable efficient data handling. -- **Smart Pointer Assignment Behavior:** When a smart pointer is assigned to a new variable, only the pointer is copied, not the data it points to. Both variables then refer to the same data. Re-instantiating the original pointer does not affect the variable holding the copied pointer. - - ```cairo - #[derive(Drop)] - struct Student { - name: ByteArray, - age: u8, - id: u32 - } +```cairo +use starknet::ClassHash; - fn main() { - let mut student1 = BoxTrait::new(Student { name: "Peter", age: 12, id: 12345 }); - let student2 = student1; - student1 = BoxTrait::new(Student { name: "James", age: 18, id: 56789 }); - println!("{}", student2.unbox().name); - } - ``` +#[starknet::interface] +pub trait IClassHashExample { + fn get_implementation_hash(self: @TContractState) -> ClassHash; + fn upgrade(ref self: TContractState, new_class_hash: ClassHash); +} - Running this code prints "Peter". +#[starknet::contract] +mod ClassHashExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::syscalls::replace_class_syscall; + use super::ClassHash; -- **Array Indexing Errors:** Attempting to access an array element out of bounds (e.g., the fifth element of a four-element array) results in a panic with an "Index out of bounds" error. + #[storage] + struct Storage { + implementation_hash: ClassHash, + } -Operator Overloading in Cairo + #[constructor] + fn constructor(ref self: ContractState, initial_class_hash: ClassHash) { + self.implementation_hash.write(initial_class_hash); + } -# Operator Overloading in Cairo + #[abi(embed_v0)] + impl ClassHashExampleImpl of super::IClassHashExample { + fn get_implementation_hash(self: @ContractState) -> ClassHash { + self.implementation_hash.read() + } -Operator overloading allows the redefinition of standard operators for user-defined types, making code more intuitive by enabling operations on custom types using familiar syntax. In Cairo, this is achieved by implementing specific traits associated with each operator. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + replace_class_syscall(new_class_hash).unwrap(); + self.implementation_hash.write(new_class_hash); + } + } +} +``` -It's important to use operator overloading judiciously to avoid making code harder to maintain. +## OpenZeppelin's Upgradeable Component -## Implementing Operator Overloading +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. -To overload an operator, you implement the corresponding trait for your custom type. For example, to overload the addition operator (`+`) for a `Potion` struct, you implement the `Add` trait. +### Usage -### Example: Combining Potions +The `UpgradeableComponent` provides: -Consider a `Potion` struct with `health` and `mana` fields. Combining two potions should add their respective fields. +- An internal `upgrade` function for safe class replacement. +- An `Upgraded` event emitted upon successful upgrade. +- Protection against upgrading to a zero class hash. ```cairo -struct Potion { - health: felt252, - mana: felt252, -} +#[starknet::contract] +mod UpgradeableContract { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_upgrades::interface::IUpgradeable; + use starknet::{ClassHash, ContractAddress}; -impl PotionAdd of Add { - fn add(lhs: Potion, rhs: Potion) -> Potion { - Potion { health: lhs.health + rhs.health, mana: lhs.mana + rhs.mana } - } -} + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); -#[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, ''); -} -``` + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; -In this example, the `add` function within the `impl Add` block takes two `Potion` instances (`lhs` and `rhs`) and returns a new `Potion` with the combined health and mana values. Overloading an operator requires specifying the concrete type being overloaded, as shown with `Add`. + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; -## Overloadable Operators in Cairo + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } -The following table lists operators, their examples, explanations, and the corresponding overloadable traits in Cairo: + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } -| 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 | | + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } -Hashing in Cairo + #[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(); -Introduction to Hashing in Cairo + // Replace the class hash upgrading the contract + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` -# Introduction to Hashing in Cairo +Listing: Integrating OpenZeppelin's Upgradeable component in a contract -Pedersen and Poseidon Hash Functions +## Security Considerations -# Pedersen and Poseidon Hash Functions +Upgrades are sensitive operations requiring careful consideration: -Hashing is the process of converting input data of any length into a fixed-size value, known as a hash. This transformation is deterministic, meaning the same input always yields the same hash. Hash functions are crucial for data storage, cryptography, data integrity verification, and are frequently used in smart contracts, particularly with Merkle trees. +- **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. -Cairo's core library provides two native hash functions: Pedersen and Poseidon. +Always ensure that only authorized roles can perform upgrades, pauses, or other critical functions. Use components like `OwnableComponent` to guard these privileged paths. -## Pedersen Hash Function +```cairo +// components +component!(path: OwnableComponent, storage: ownable); +component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); -Pedersen hash functions are cryptographic algorithms based on elliptic curve cryptography. They perform operations on points along an elliptic curve, making them easy to compute in one direction but computationally difficult to reverse, based on the Elliptic Curve Discrete Logarithm Problem (ECDLP). This one-way property ensures their security for cryptographic purposes. +#[abi(embed_v0)] +impl OwnableImpl = OwnableComponent::OwnableImpl; +impl InternalUpgradeableImpl = UpgradeableComponent::InternalImpl; -## Poseidon Hash Function +#[event] +fn Upgraded(new_class_hash: felt252) {} -Poseidon is a family of hash functions optimized for efficiency within algebraic circuits, making it ideal for Zero-Knowledge proof systems like STARKs (and thus Cairo). It employs a "sponge construction" using the Hades permutation. Cairo's Poseidon implementation uses a three-element state permutation with specific parameters. +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 +} +``` -## When to Use Them +L1-L2 Messaging -Pedersen was initially used on Starknet for tasks like computing storage variable addresses (e.g., in `LegacyMap`). However, Poseidon is now recommended for Cairo programs as it is cheaper and faster when working with STARK proofs. +# L1-L2 Messaging -## Working with Hashes in Cairo +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. -The `core::hash` module provides the necessary traits and functions for hashing. +Key characteristics of Starknet's messaging system: -### The `Hash` Trait +- **Asynchronous**: Results of messages sent to the other chain cannot be awaited within the contract code execution. +- **Asymmetric**: + - `L1->L2`: Messages are automatically delivered to the target L2 contract by the Starknet sequencer. + - `L2->L1`: Only the hash of the message is sent to L1 by the sequencer; it must be consumed manually via an L1 transaction. -The `Hash` trait is implemented for types convertible to `felt252`, including `felt252` itself. For structs, deriving `Hash` allows them to be hashed if all their fields are hashable. Types like `Array` or `Felt252Dict` prevent deriving `Hash`. +## The StarknetMessaging Contract -### Hash State Traits +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. -`HashStateTrait` and `HashStateExTrait` define methods for managing hash states: +```js +interface IStarknetMessaging is IStarknetMessagingEvents { -- `update(self: S, value: felt252) -> S`: Updates the hash state with a `felt252` value. -- `finalize(self: S) -> felt252`: Completes the hash computation and returns the final hash value. -- `update_with(self: S, value: T) -> S`: Updates the hash state with a value of type `T`. + function sendMessageToL2( + uint256 toAddress, + uint256 selector, + uint256[] calldata payload + ) external returns (bytes32); -```cairo -/// A trait for hash state accumulators. -trait HashStateTrait { - fn update(self: S, value: felt252) -> S; - fn finalize(self: S) -> felt252; -} + function consumeMessageFromL2(uint256 fromAddress, uint256[] calldata payload) + external + returns (bytes32); -/// Extension trait for hash state accumulators. -trait HashStateExTrait { - /// Updates the hash state with the given value. - fn update_with(self: S, value: T) -> S; -} + function startL1ToL2MessageCancellation( + uint256 toAddress, + uint256 selector, + uint256[] calldata payload, + uint256 nonce + ) external; -/// 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; + function cancelL1ToL2Message( + uint256 toAddress, + uint256 selector, + uint256[] calldata payload, + uint256 nonce + ) external; } ``` -### Hashing Examples + Starknet messaging contract interface -To hash data, you first initialize a hash state using `PoseidonTrait::new()` or `PedersenTrait::new(base: felt252)`. Then, you update the state using `update` or `update_with`, and finally call `finalize`. +### Sending Messages from Ethereum to Starknet (L1->L2) -#### Poseidon Hashing Example +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. -This example demonstrates hashing a struct using the Poseidon function. - -```cairo -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::PoseidonTrait; +**Fees**: -#[derive(Drop, Hash)] -struct StructForHash { - first: felt252, - second: felt252, - third: (u32, u32), - last: bool, -} +- 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`. -#[executable] -fn main() -> felt252 { - let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; +The `sendMessageToL2` function signature is: - let hash = PoseidonTrait::new().update_with(struct_to_hash).finalize(); - hash -} +```js +function sendMessageToL2( + uint256 toAddress, + uint256 selector, + uint256[] calldata payload +) external override returns (bytes32); ``` -#### Pedersen Hashing Example +- `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). -Pedersen requires a base state. You can either hash the struct with an arbitrary base state or serialize it into an array to hash its elements sequentially. +**Example (Solidity)**: -```cairo -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::pedersen::PedersenTrait; +```js +// Sends a message on Starknet with a single felt. +function sendMessageFelt( + uint256 contractAddress, + uint256 selector, + uint256 myFelt +) + external + payable +{ + // We "serialize" here the felt into a payload, which is an array of uint256. + uint256[] memory payload = new uint256[](1); + payload[0] = myFelt; -#[derive(Drop, Hash, Serde, Copy)] -struct StructForHash { - first: felt252, - second: felt252, - third: (u32, u32), - last: bool, + // msg.value must always be >= 20_000 wei. + _snMessaging.sendMessageToL2{value: msg.value}( + contractAddress, + selector, + payload + ); } +``` -#[executable] -fn main() -> (felt252, felt252) { - let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; - - // hash1 is the result of hashing a struct with a base state of 0 - let hash1 = PedersenTrait::new(0).update_with(struct_to_hash).finalize(); - - 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); +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. - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } +**Example (Cairo)**: - // hash2 is the result of hashing only the fields of the struct - let hash2 = state.finalize(); +```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"); - (hash1, hash2) + // You can now use the data, automatically deserialized from the message payload. + assert!(my_felt == 123, "Invalid value"); } ``` -## Poseidon Builtin +### Sending Messages from Starknet to Ethereum (L2->L1) -The Poseidon builtin computes cryptographic hashes using the Poseidon hash function, optimized for zero-knowledge proofs and algebraic circuits. It utilizes the Hades permutation strategy, combining full and partial rounds for security and performance in STARK proofs. +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. -Poseidon offers: +**Example (Cairo)**: -- Better performance than Pedersen for multiple inputs. -- A ZK-friendly design optimized for constraints in ZK proof systems. -- Strong cryptographic security. - -### Cells Organization +```cairo,noplayground + 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`. + syscalls::send_message_to_l1_syscall(to_address.into(), array![my_felt].span()) + .unwrap(); + } +``` -The Poseidon builtin uses a dedicated memory segment and follows a deduction property: +The `send_message_to_l1_syscall` arguments are: -- **Input cells [0-2]:** Store input state for the Hades permutation. -- **Output cells [3-5]:** Store the computed permutation results. +- `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. -Each operation involves 6 consecutive cells (3 inputs, 3 outputs). Reading an output cell triggers the VM to apply the Hades permutation to the input cells and populate the output cells. +**Example (Cairo `send_message_to_l1` syscall)**: -#### Single Value Hashing Example +```cairo,noplayground +pub extern fn send_message_to_l1_syscall( + to_address: felt252, payload: Span, +) -> SyscallResult<()> implicits(GasBuiltin, System) nopanic; +``` -For hashing a single value (e.g., 42): +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. -1. The value is written to the first input cell (position 3:0). -2. Other input cells default to 0. -3. When an output cell (e.g., 3:3) is read, the VM computes the permutation. +**Example (Solidity)**: -Implementing Hashing in Cairo +```js +function consumeMessageFelt( + uint256 fromAddress, + uint256[] calldata payload +) + external +{ + let messageHash = _snMessaging.consumeMessageFromL2(fromAddress, payload); -### Hashing Arrays with Poseidon + // You can use the message hash if you want here. -To hash an `Array` or a struct containing a `Span`, you can use the built-in function `poseidon_hash_span(mut span: Span) -> felt252`. + // We expect the payload to contain only a felt252 value (which is a uint256 in Solidity). + require(payload.length == 1, "Invalid payload"); -First, import the required traits and function: + uint256 my_felt = payload[0]; -```cairo,noplayground -use core::hash::{HashStateExTrait, HashStateTrait}; -use core::poseidon::{PoseidonTrait, poseidon_hash_span}; + // From here, you can safely use `my_felt` as the message has been verified by StarknetMessaging. + require(my_felt > 0, "Invalid value"); +} ``` -Define the struct. Note that deriving the `Hash` trait for a struct with a non-hashable field like `Span` will result in an error. +Note that `consumeMessageFromL2` uses `msg.sender` internally to compute the message hash, so the Solidity contract calling it must match the `to_address` provided in the L2 `send_message_to_l1_syscall`. -```cairo, noplayground -#[derive(Drop)] -struct StructForHashArray { - first: felt252, - second: felt252, - third: Array, -} -``` +## Cairo Serde -The following example demonstrates hashing a struct containing an array. A `HashState` is initialized and updated with the struct's fields. The hash of the `Array` is computed using `poseidon_hash_span` on its span, and then this hash is used to update the main `HashState` before finalizing. +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. -```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] }; +For example, a `u256` type in Cairo is represented as: - 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(); +```cairo,does_not_compile +struct u256 { + low: u128, + high: u128, } -# -# ``` -Function Inlining and Macros - -Function Inlining: Concepts, Attributes, and Performance - -## Function Inlining: Concepts, Attributes, and Performance - -Inlining is a code optimization technique where a function call is replaced with the actual code of the called function at the call site. This eliminates function call overhead, potentially improving performance by reducing executed instructions, though it may increase program size. - -### The `inline` Attribute - -The `inline` attribute in Cairo suggests whether the Sierra code of a function should be injected into the caller's context instead of using a `function_call` libfunc. The attribute has three variants: - -- `#[inline]`: Suggests performing an inline expansion. -- `#[inline(always)]`: Suggests that an inline expansion should always be performed. -- `#[inline(never)]`: Suggests that an inline expansion should never be performed. +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. -These attributes are hints and may be ignored by the compiler, although `#[inline(always)]` is rarely ignored. Annotating functions with `#[inline(always)]` can reduce the total steps required for function calls by avoiding the overhead of calling and argument passing. +**Example (Solidity serialization of `u256`)**: -However, inlining can increase code size due to code duplication at call sites. It is most beneficial for small, frequently called functions, especially those with many arguments, as inlining large functions can significantly increase code length. - -### Inlining Decision Process - -The Cairo compiler uses heuristics for functions without explicit inline directives. It calculates a function's "weight" using `ApproxCasmInlineWeight` to estimate the generated Cairo Assembly (CASM) statements. If the weight is below a threshold, the function is inlined. Functions with fewer raw statements than the threshold are also typically inlined. - -Special cases include very simple functions (e.g., those that only call another function or return a constant), which are always inlined. Conversely, functions with complex control flow or those ending with `Panic` are generally not inlined. - -### Inlining Example +```js +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; +``` -Listing 12-5 demonstrates inlining: +For more details, refer to the [Starknet documentation][starknet messaging doc] or the [detailed guide][glihm messaging guide]. -```cairo -#[executable] -fn main() -> felt252 { - inlined() + not_inlined() -} +[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 -#[inline(always)] -fn inlined() -> felt252 { - 1 -} +Oracles and Randomness -#[inline(never)] -fn not_inlined() -> felt252 { - 2 -} -``` +# Oracles and Randomness -Listing 12-5: A small Cairo program that adds the return value of 2 functions, with one of them being inlined +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. -The corresponding Sierra code shows that `not_inlined` is called using `call rel 9`, while `inlined`'s code is directly injected (inlined) without a `call` instruction. +## Oracles -### Additional Optimizations Example +Oracles act as secure intermediaries, transmitting external data to blockchains and smart contracts. This section details interactions with the Pragma oracle on Starknet. -Listing 12-6 shows a program where an inlined function's return value is unused: +### Pragma Oracle for Price Feeds -```cairo -#[executable] -fn main() { - inlined(); - not_inlined(); -} +Pragma is a zero-knowledge oracle providing verifiable off-chain data on Starknet. -#[inline(always)] -fn inlined() -> felt252 { - 'inlined' -} +#### Setting Up Your Contract for Price Feeds -#[inline(never)] -fn not_inlined() -> felt252 { - 'not inlined' -} -``` +1. **Add Pragma as a Project Dependency**: + Edit your `Scarb.toml` file: + ```toml + [dependencies] + pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" + ``` -Listing 12-6: A small Cairo program that calls `inlined` and `not_inlined` and doesn't return any value. +Development Tools and Best Practices -In this case, the compiler optimized the `main` function by omitting the `inlined` function's code entirely because its return value was not used. This reduced code length and execution steps. The `not_inlined` function was called normally using `function_call`. +# Development Tools and Best Practices -Inline Macros and Compile-Time Generation +### Starkli Tooling -# Inline Macros and Compile-Time Generation +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`. -Procedural Macros in Cairo +To retrieve a smart wallet's class hash using `starkli`, use: -# Procedural Macros in Cairo +```bash +starkli class-hash-at --rpc http://0.0.0.0:5050 +``` -Procedural macros in Cairo allow you to write code that generates other code at compile time, extending Cairo's capabilities through metaprogramming. +### Contract Deployment -## The Difference Between Macros and Functions +Declare contracts using the `starkli declare` command: -Macros, unlike functions, can: +```bash +starkli declare target/dev/listing_99_12_vote_contract_Vote.contract_class.json --rpc http://0.0.0.0:5050 --account katana-0 +``` -- Accept a variable number of parameters. -- Operate at compile time, enabling actions like trait implementation, which functions cannot do as they are called at runtime. +If you encounter `compiler-version` errors, ensure `starkli` is updated or specify the compiler version using `--compiler-version x.y.z`. -However, Cairo macros are more complex to write and maintain because they are written in Rust and operate on Cairo code. +The class hash for a contract can be verified. For example, `0x06974677a079b7edfadcd70aa4d12aac0263a4cda379009fca125e0ab1a9ba52` is a known class hash. -## Cairo Procedural Macros are Rust Functions +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. -Procedural macros in Cairo are essentially Rust functions that transform Cairo code. These functions take Cairo code as input and return modified Cairo code. Implementing macros requires a package with both `Cargo.toml` (for macro implementation dependencies) and `Scarb.toml` (to mark the package as a macro). +### Smart Contract Best Practices -The core types manipulated by these functions are: +#### Deployment Safety -- `TokenStream`: Represents a sequence of Cairo tokens (smallest code units like keywords, identifiers, operators). +- **`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. -## Creating an expression Macros +#### Bridging Safety (L1-L2 Interactions) -To create an expression macro, you can leverage Rust crates like `cairo_lang_macro`, `cairo_lang_parser`, and `cairo_lang_syntax`. These crates allow manipulation of Cairo syntax at compile time. +- **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. -An example is the `pow` macro from the Alexandria library, which computes a number raised to a power at compile time. The macro implementation parses the input tokens to extract the base and exponent, performs the calculation using `BigDecimal::pow`, and returns the result as a `TokenStream`. + ```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… + } + ``` -```rust, noplayground -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()); - } - }; +#### Economic/DoS & Griefing Vulnerabilities - // 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()); - } - }; +- **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. - // base^exp - let result: BigDecimal = pow(base, exp); +Cairo Virtual Machine (VM) and Execution - ProcMacroResult::new(TokenStream::new(result.to_string())) -} -``` +Introduction to Cairo and its Virtual Machine (VM) -Derive and Attribute Macros in Cairo +# Introduction to Cairo and its Virtual Machine (VM) -# Derive and Attribute Macros in Cairo +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. -Derive and attribute macros in Cairo allow for custom code generation, automating repetitive tasks and extending the language's capabilities. +## Virtual Machine -## Derive Macros +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). -Derive macros enable the automatic implementation of traits for types. When a type is annotated with `#[derive(TraitName)]`, the macro: +There are two types of VMs: -1. Receives the type's structure. -2. Applies custom logic to generate the trait implementation. -3. Outputs the generated implementation code. +- **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. -This process eliminates the need for manual, repetitive trait implementations. +Cairo VM Architecture and Execution Flow -### Creating a Derive Macro +# Cairo VM Architecture and Execution Flow -The following example demonstrates a derive macro that implements a `Hello` trait, which includes a `hello()` function that prints "Hello, StructName!". +## Execution Model -First, the `Hello` trait needs to be defined: +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. -```cairo -trait Hello { - fn hello(self: @T); -} -``` +### Registers -The macro implementation (`hello_macro`) parses the input token stream, extracts the struct name, and generates the `Hello` trait implementation for that struct. +The Cairo VM has three dedicated registers: -```rust, noplayground -use cairo_lang_macro::{derive_macro, ProcMacroResult, TokenStream}; -use cairo_lang_parser::utils::SimpleParserDatabase; -use cairo_lang_syntax::node::kind::SyntaxKind::{TerminalStruct, TokenIdentifier}; - -#[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); - - 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; - } - } +- **`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. - ProcMacroResult::new(TokenStream::new(indoc::formatdoc! {r#" - impl SomeHelloImpl of Hello<{0}> {{ - fn hello(self: @{0}) {{ - println!("Hello {0}!"); - }} - }} - "#, struct_name})) -} -``` +The state of the VM at any moment is defined by the values of these three registers. -To use this macro, add `hello_macro = { path = "path/to/hello_macro" }` to your `Scarb.toml`'s `[dependencies]` and apply it to a struct: +## Instructions and Opcodes -```cairo, noplayground -#[derive(HelloMacro, Drop, Destruct)] -struct SomeType {} -``` +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. -Then, the `hello()` function can be called on an instance of `SomeType`: +The VM supports three core opcodes: -```cairo, noplayground -# #[executable] -# fn main() { - let a = SomeType {}; - a.hello(); -# -# let res = pow!(10, 2); -# println!("res : {}", res); -# -# let _a = RenamedType {}; -# } -# -# #[derive(HelloMacro, Drop, Destruct)] -# struct SomeType {} -# -# #[rename] -# struct OldType {} -# -# trait Hello { -# fn hello(self: @T); -# } -# -# -``` +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. -_Note: The `Hello` trait must be defined or imported in the code._ +### Cairo Assembly (CASM) -## Attribute Macros +CASM is the human-readable assembly language for Cairo, representing machine instructions textually. Each line of CASM corresponds to a specific instruction. -Attribute-like macros offer more flexibility than derive macros, as they can be applied to various items, including functions, not just structs and enums. They are useful for diverse code generation tasks like renaming items, modifying function signatures, or executing code conditionally. +## State Transition -Attribute macros have a signature that accepts two `TokenStream` arguments: `attr` for attribute arguments and `code` for the item the attribute is applied to. +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. -```rust, noplayground -#[attribute_macro] -pub fn attribute(attr: TokenStream, code: TokenStream) -> ProcMacroResult {} -``` +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. -### Creating an Attribute Macro +### Deterministic and Non-deterministic Cairo Machine -The following example shows a `rename` attribute macro that renames a struct. +- **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. -```rust, noplayground -use cairo_lang_macro::attribute_macro; -use cairo_lang_macro::{ProcMacroResult, TokenStream}; +The Cairo Runner is the entrypoint for running a Cairo program and generating the AIR inputs needed for proof. -#[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"), - )) -} -``` +## Circuit Evaluation -To use this macro, add `rename_macro = { path = "path/to/rename_macro" }` to your `Scarb.toml` and apply it to a struct: +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. + +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)`. ```cairo -# #[executable] -# fn main() { -# let a = SomeType {}; -# a.hello(); +# 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 res = pow!(10, 2); -# println!("res : {}", res); +# let add = circuit_add(a, b); +# let mul = circuit_mul(a, add); # -# let _a = RenamedType {}; -# } +# let output = (mul,); # -# #[derive(HelloMacro, Drop, Destruct)] -# struct SomeType {} +# let mut inputs = output.new_inputs(); +# inputs = inputs.next([10, 0, 0, 0]); +# inputs = inputs.next([20, 0, 0, 0]); +# +# let instance = inputs.done(); # -#[rename] -struct OldType {} +# let bn254_modulus = TryInto::<_, +# CircuitModulus, +# >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) +# .unwrap(); # -# trait Hello { -# fn hello(self: @T); -# } + 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(); +# } ``` -Project Configuration and Macro Usage +## Program Execution Flow -## Project Configuration and Macro Usage +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. -To define and use macros in Cairo projects, specific configurations are required in your project manifests. +Each instruction and its arguments increment the Program Counter (PC) by 1. Instructions like `call` and `ret` manage a function stack. -### Macro Definition Project Configuration +- **`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`. -For the project that defines the macro, the configuration involves both `Cargo.toml` and `Scarb.toml`. +For example: -**`Cargo.toml` Requirements:** -The `Cargo.toml` file must include `crate-type = ["cdylib"]` under the `[lib]` target and list `cairo-lang-macro` in `[dependencies]`. +- `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. -```toml -[package] -name = "pow" -version = "0.1.0" -edition = "2021" -publish = false +## Memory Model -[lib] -crate-type = ["cdylib"] +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. -[dependencies] -bigdecimal = "0.4.5" -cairo-lang-macro = "0.1.1" -cairo-lang-parser = "2.11.4" -cairo-lang-syntax = "2.11.4" +### Relocatable Values and Memory Space + +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. -[workspace] +Example memory and relocation table: + +``` +Addr Value +----------- +// Segment 0 +0:0 5189976364521848832 +0:1 10 +... +// Segment 1 +1:0 2:0 +... +// Segment 2 +2:0 110 ``` -**`Scarb.toml` Requirements:** -The `Scarb.toml` file must define a `[cairo-plugin]` target type. +**From relocation value to one contiguous memory address space:** -```toml -[package] -name = "pow" -version = "0.1.0" +``` +Addr Value +----------- +1 5189976364521848832 +2 10 +... +13 22 +14 23 +... +22 110 +``` + +**Relocation table:** -[cairo-plugin] +``` +segment_id starting_index +---------------------------- +0 1 +1 13 +2 22 ``` -Additionally, the project needs a Rust library file (`src/lib.rs`) that implements the procedural macro API. Notably, the project defining the macro does not require any Cairo code. +Cairo VM Memory Model -### Using Your Macro in Another Project +# Cairo VM Memory Model -To use a macro defined in another package, add that package to the `[dependencies]` section of your project's `Scarb.toml`. +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. -**Example `Scarb.toml` for Using Macros:** +## Overview -```toml -[package] -name = "no_listing_15_procedural_macro" -version = "0.1.0" -edition = "2024_07" +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. -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +## Non-Deterministic Read-only Memory -[dependencies] -cairo_execute = "2.11.4" -pow = { path = "../no_listing_16_procedural_macro_expression" } -hello_macro = { path = "../no_listing_17_procedural_macro_derive" } -rename_macro = { path = "../no_listing_18_procedural_macro_attribute" } +Cairo employs a non-deterministic, read-only memory model, which effectively behaves as a write-once memory: +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. -[dev-dependencies] -cairo_test = "2.11.4" +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. -[cairo] -enable-gas = false +## Memory Segments +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**. -[[target.executable]] -name = "main" -function = "no_listing_15_procedural_macro::main" -``` +Cairo's memory model includes the following segments: -### Expression Macros +- **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. -Expression macros offer enhanced capabilities beyond regular functions, allowing them to: +Segments 1 onwards (Execution, Builtin, User) have dynamic address spaces whose lengths are unknown until program completion. -- Accept a variable number of arguments. -- Handle arguments of different types. +The standard layout of memory segments is: -Cairo Virtual Machine (VM) +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 -Introduction to the Cairo Virtual Machine +The number of builtin and user segments is dynamic. -# Introduction to the Cairo Virtual Machine +## Relocation -Ever wondered how your Cairo programs were executed? +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. -First, they are compiled by the Cairo Compiler, then executed by the Cairo Virtual Machine, or _Cairo VM_ for short, which generates a trace of execution, used by the Prover to generate a STARK proof of that execution. This proof can later be verified by a Verifier. +Consider the following Cairo Zero program example: -The following chapters will go deep inside the inner workings of the Cairo VM. We'll cover its architecture, its memory model, and its execution model. Next, we'll explore builtins and hints, their purpose, and how they work. Finally, we'll look at the runner, which orchestrates the execution of a Cairo program. +```cairo +%builtins output -## Virtual Machine +func main(output_ptr: felt*) -> (output_ptr: felt*) { + + // We are allocating three different values to segment 1. + [ap] = 10, ap++; + [ap] = 100, ap++; + [ap] = [ap - 2] + [ap - 1], ap++; -Virtual Machines (VMs) are software emulations of physical computers. They provide a complete programming environment through an API which includes everything required for the correct execution of programs above it. + // 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++; -Every virtual machine API includes an instruction set architecture (ISA) in which to express programs. It could be the same instruction set as some physical machine (e.g. RISC-V), or a dedicated one implemented in the VM (e.g. Cairo assembly, CASM). + // Asserts that output_ptr equals to 110. + assert [output_ptr] = 110; -Those that emulate an OS are called _System Virtual Machines_, such as Xen and VMWare. We're not interested in them here. + // Returns the output_ptr + 1 as the next unused memory address. + return (output_ptr=output_ptr + 1); +} +``` -The other ones we're interested in are _Process Virtual Machines_. They provide the environment needed by a single user-level process. +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. -### Process Virtual Machines (Example: JVM) +## Built-in Segments (Output and Segment Arena) -The most well-known process VM might be the Java Virtual Machine (JVM). +### Output Builtin -- Given a Java program `prgm.java`, it is compiled into a class `prgm.class`, containing _Java bytecode_ (JVM instructions and metadata). -- The JVM verifies that the bytecode is safe to run. -- The bytecode is either interpreted (slow) or compiled to machine code just in time (JIT, fast). -- If using JIT, the bytecode is translated to machine code while executing the program. -- Java programs could also be directly compiled to a specific CPU architecture (read machine code) through a process called _ahead-of-time compilation_ (AOT). +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. -### Process Virtual Machines (Cairo VM) +Its role in STARK proofs includes: -The Cairo VM is also a process VM, similar to the JVM, with one significant difference: Java and its JVM are designed for (platform-independent) general-purpose computing, while Cairo and its Cairo VM are specifically designed for (platform-independent) _provable_ general-purpose computing. +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. -- A Cairo program `prgm.cairo` is compiled into compilation artifacts `prgm.json`, containing _Cairo bytecode_ (encoded CASM, the Cairo instruction set, and extra data). -- As seen in the [introduction](ch00-00-introduction.md), Cairo Zero directly compiles to CASM while Cairo first compiles to _Sierra_ and then to a safe subset of CASM. -- The Cairo VM _interprets_ the provided CASM and generates a trace of the program execution. -- The obtained trace data can be fed to the Cairo Prover in order to generate a STARK proof, allowing to prove the correct execution of the program. Creating this _validity proof_ is the main purpose of Cairo. +### Segment Arena Builtin -Cairo VM Architecture and Instruction Set +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. -# Cairo VM Architecture and Instruction Set +Key validation rules for the Segment Arena include: -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 compatible with other backends. The Cairo Virtual Machine (CairoVM) is a core component of the Cairo ecosystem, responsible for processing compilation artifacts and executing instructions. +- 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 VM Components +Cairo Builtins: Cryptographic and Arithmetic Operations -The Cairo ecosystem consists of three main components: +### Cairo Builtins: Cryptographic and Arithmetic Operations -1. **The Cairo compiler**: Transforms Cairo source code into Cairo bytecode, also known as compilation artifacts. -2. **The Cairo Virtual Machine (CairoVM)**: Executes the compilation artifacts, producing the Arithmetic Intermediate Representation (AIR) private input (witness) and AIR public input required for proof generation. -3. **The Cairo prover and verifier**: Verifies that the constraints defined by the Cairo AIR hold for the CairoVM outputs. +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. -## Cairo Machine +#### Keccak Builtin -The Cairo machine is a theoretical model defining a Von Neumann architecture for proving arbitrary computations. It is characterized by two core models: +The Keccak builtin computes the Keccak-256 hash of a given input. -- **CPU (Execution Model)**: Specifies the Instruction Set Architecture (ISA), including the instruction set, registers (`pc`, `ap`, `fp`), and the state transition algorithm. -- **Memory Model**: Defines how the CPU interacts with memory, which stores both program and instruction data. +##### Syscall Signature -Cairo implements a custom zero-knowledge ISA (ZK-ISA) optimized for proof generation and verification, unlike general-purpose ISAs. +```cairo,noplayground +pub extern fn keccak_syscall( + input: Span, +) -> SyscallResult implicits(GasBuiltin, System) nopanic; +``` -### Deterministic and Non-deterministic Cairo Machine +##### Description -The Cairo machine exists in two versions: +Computes the Keccak-256 hash of a given input. -- **Deterministic machine**: Used by the prover. It takes a trace and memory, verifying state transitions. It returns `accept` if all transitions are valid, `reject` otherwise. -- **Non-deterministic machine**: Used by the verifier. It relies on the deterministic machine and takes the initial state. +##### Arguments -## Instructions and Opcodes +- `input`: A `Span` representing the Keccak-256 input. -A Cairo **instruction** is a 64-bit field element representing a single computational step. It contains three 16-bit signed offsets (`off_dst`, `off_op0`, `off_op1`) and 15 boolean flags that dictate register usage for addressing, arithmetic operations, and register updates (`pc`, `ap`, `fp`). +##### Return Values -The VM supports three primary opcodes: +- `SyscallResult`: The computed hash result. -1. **`CALL`**: Initiates a function call, saving the current context (`fp` and return `pc`) to the stack. -2. **`RET`**: Executes a function return, restoring the caller's context from the stack. -3. **`ASSERT_EQ`**: Enforces an equality constraint. +##### Deduction Property -## Cairo Assembly (CASM) +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. -CASM is the human-readable assembly language for Cairo, representing machine instructions. Developers write in high-level Cairo, and the compiler translates it into CASM. CASM instructions can also be written manually, with examples like `[fp + 1] = [ap - 2] + 5` or `jmp rel 17 if [ap] != 0`. +##### Error Conditions -Cairo VM Memory Model +- 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. -# Cairo VM Memory Model +#### ECDSA Builtin -The Cairo VM memory model is designed to efficiently represent memory values for STARK proof generation. It possesses two primary characteristics: +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. -### Non-Determinism +##### Memory Organization -In Cairo, memory addresses and their values are not managed by a traditional memory system. Instead, the prover asserts the location and the values stored at those addresses. This means the prover directly states "at memory address X, the value Y is stored," eliminating the need for explicit checks as found in read-write memory models. +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. -### Read-Only and Write-Once +##### Cell Layout in the Memory Segment -Cairo's memory is read-only, meaning values do not change once set during program execution. This effectively makes it a write-once memory model: once a value is assigned to an address, it cannot be overwritten. Subsequent operations are limited to reading or verifying the existing value. +The ECDSA segment arranges cells in pairs: -### Contiguous Memory Space +- **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`. -The memory address space in Cairo is contiguous. If a program accesses memory addresses `x` and `y`, it cannot skip any addresses located between `x` and `y`. +##### Signature Verification Process -### Relocatable Values and Contiguous Address Space +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: -Relocatable values, which are initially stored in different segments, are transformed into a single contiguous memory address space using a relocation table. This table provides context by mapping segment identifiers to their starting indices. +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. -For instance, a program might output values stored across different segments. After execution, these are resolved into a linear address space. +##### Error Conditions -**Example Relocatable Values:** +- **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. -``` -Addr Value +#### Poseidon Builtin -// Segment 0 -0:0 5189976364521848832 -0:1 10 -0:2 5189976364521848832 -0:3 100 -0:4 5201798304953696256 -0:5 5191102247248822272 -0:6 5189976364521848832 -0:7 110 -0:8 4612389708016484351 -0:9 5198983563776458752 -0:10 1 -0:11 2345108766317314046 -⋮ -// Segment 1 -1:0 2:0 -1:1 3:0 -1:2 4:0 -1:3 10 -1:4 100 -1:5 110 -1:6 2:0 -1:7 110 -1:8 2:1 -⋮ -// Segment 2 -2:0 110 -``` +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. -**From Relocation Value to One Contiguous Memory Address Space:** +##### Cells Organization -``` -Addr Value ------------ -1 5189976364521848832 -2 10 -3 5189976364521848832 -4 100 -5 5201798304953696256 -6 5191102247248822272 -7 5189976364521848832 -8 110 -9 4612389708016484351 -10 5198983563776458752 -11 1 -12 2345108766317314046 -13 22 -14 23 -15 23 -16 10 -17 100 -18 110 -19 22 -20 110 -21 23 -22 110 -``` +The Poseidon builtin operates with a dedicated memory segment and follows a deduction property pattern with 6 consecutive cells: -**Relocation Table:** +- **Input cells [0-2]**: Store input state for the Hades permutation. +- **Output cells [3-5]**: Store computed permutation results. -``` -segment_id starting_index ----------------------------- -0 1 -1 13 -2 22 -3 23 -4 23 -``` +##### How it Works -Cairo Intermediate Representations and the VM +Each operation works with a block of 3 inputs followed by 3 outputs. When a program reads any output cell, the VM applies the Hades permutation to the input cells and populates all three output cells with the results. -# Cairo Intermediate Representations and the VM +##### Single Value Hashing Example -Starting with Starknet Alpha v0.11.0, compiled Cairo contracts produce an intermediate representation called Safe Intermediate Representation (Sierra). The sequencer then compiles Sierra into Cairo Assembly (Casm) for execution by the Starknet OS. +For hashing a single value (e.g., 42): -## Why Casm? +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. -Starknet, as a validity rollup, requires proofs for block execution. STARK proofs operate on polynomial constraints. Cairo bridges this gap by translating Cairo instructions into polynomial constraints that enforce correct execution according to its defined semantics, enabling the proof of block validity. +##### Sequence Hashing Example -## Sierra Code Structure +For hashing a sequence of values (e.g., 73, 91): -A Sierra file consists of three main parts: +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). -1. **Type and libfunc declarations**: Defines the types and library functions used. -2. **Statements**: The core instructions of the program. -3. **Function declarations**: Declares the functions within the program. +##### Error Condition -The statements in the Sierra code directly correspond to the order of function declarations. +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. -### Example Sierra Code Breakdown +#### Mod Builtin (AddMod, MulMod) -Consider the following Sierra code: +The Mod Builtin handles modular arithmetic operations—specifically addition and multiplication—on field elements within a given modulus `p`. It comes in two derivations: `AddModBuiltin` for addition and `MulModBuiltin` for multiplication. -```cairo,noplayground -// type declarations -type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false] - -// libfunc declarations -libfunc function_call = function_call -libfunc felt252_const<1> = felt252_const<1> -libfunc store_temp = store_temp -libfunc felt252_add = felt252_add -libfunc felt252_const<2> = felt252_const<2> - -// statements -00 function_call() -> ([0]) -01 felt252_const<1>() -> ([1]) -02 store_temp([1]) -> ([1]) -03 felt252_add([1], [0]) -> ([2]) -04 store_temp([2]) -> ([2]) -05 return([2]) -06 felt252_const<1>() -> ([0]) -07 store_temp([0]) -> ([0]) -08 return([0]) -09 felt252_const<2>() -> ([0]) -10 store_temp([0]) -> ([0]) -11 return([0]) - -// funcs -main::main::main@0() -> (felt252) -main::main::inlined@6() -> (felt252) -main::main::not_inlined@9() -> (felt252) -``` - -This code demonstrates: - -- The `main` function starts at line 0 and returns a `felt252` on line 5. -- The `inlined` function starts at line 6 and returns a `felt252` on line 8. -- The `not_inlined` function starts at line 9 and returns a `felt252` on line 11. - -The statements for the `main` function are located between lines 0 and 5: +##### Under the Hood -```cairo,noplayground -00 function_call() -> ([0]) -01 felt252_const<1>() -> ([1]) -02 store_temp([1]) -> ([1]) -03 felt252_add([1], [0]) -> ([2]) -04 store_temp([2]) -> ([2]) -05 return([2]) -``` +- **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`. -Cairo VM Architecture and Components +##### Error Conditions -Introduction to Cairo VM and its Purpose +- **`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`. -# Introduction to Cairo VM and its Purpose +#### Pedersen Builtin -Cairo Language and Provability +The Pedersen builtin is dedicated to computing the Pedersen hash of two field elements (felts). -# Cairo Language and Provability +##### Cells Organization -## Sierra and Provability +The Pedersen builtin has its own dedicated memory segment and is organized in triplets of cells: -Sierra acts as an intermediary layer between user code and the provable statement, ensuring that all transactions are eventually provable. +- **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. -### Safe Casm +##### Error Conditions -The mechanism by which Sierra guarantees provability is by compiling Sierra instructions into a subset of Casm known as "safe Casm." The critical property of safe Casm is its provability for all possible inputs. +- 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. -A key consideration in designing the Sierra to Casm compiler is handling potential failures gracefully. For instance, using `if/else` instructions is preferred over `assert` to ensure that all failures are handled without breaking provability. +#### Other Builtins -#### Example: `find_element` Function +The Cairo VM implements a variety of other built-ins, each serving specific purposes in computation and proof generation. The following table lists them: -Consider the `find_element` function from the Cairo Zero common library: +| 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. | -```cairo -func find_element{range_check_ptr}(array_ptr: felt*, elm_size, n_elms, key) -> (elm_ptr: felt*) { - alloc_locals; - local index; - % { - ... - %} - assert_nn_le(a=index, b=n_elms - 1); - tempvar elm_ptr = array_ptr + elm_size * index; - assert [elm_ptr] = key; - return (elm_ptr=elm_ptr); -} -``` +[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 -This function, as written, can only execute correctly if the requested element exists within the array. If the element is not found, the `assert` statements would fail for all possible hint values, rendering the code non-provable. +Cairo Builtins: Specialized Functions -The Sierra to Casm compiler is designed to prevent the generation of such non-provable Casm. Furthermore, simply substituting `assert` with `if/else` is insufficient, as it can lead to non-deterministic execution, where the same input might produce different results depending on hint values. +# Cairo Builtins: Specialized Functions -Cairo VM Memory Model +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. -# Cairo VM Memory Model +## How Builtins Work -Cairo's memory model is designed for efficiency in proof generation, differing from the EVM's read-write model. It requires only 5 trace cells per memory access, making the cost proportional to the number of accesses rather than addresses used. Rewriting to an existing memory cell has a similar cost to writing to a new one. This model simplifies proving program correctness by enforcing immutability of allocated memory after the first write. +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. -## Introduction to Segments +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. -Cairo organizes memory addresses into **segments**, allowing dynamic expansion of memory segments at runtime while ensuring allocated memory remains immutable. +### Validation Property -1. **Relocatable Values**: During runtime, memory addresses are grouped into segments, each with a unique identifier and an offset, represented as `:`. -2. **Relocation Table**: At the end of execution, these relocatable values are transformed into a single, contiguous memory address space, with a separate relocation table providing context. +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. -### Segment Values +The Range Check builtin validates values immediately upon writing to a cell, enabling early detection of out-of-range values. -Cairo's memory model includes the following segments: +#### Valid Operation Example -- **Program Segment**: Stores the bytecode (instructions) of a Cairo program. The Program Counter (`pc`) starts here. -- **Execution Segment**: Stores runtime data such as temporary variables, function call frames, and pointers. The Allocation Pointer (`ap`) and Frame Pointer (`fp`) start here. -- **Builtin Segment**: Stores actively used builtins. Each builtin has its own dynamically allocated segment. -- **User Segment**: Stores program outputs, arrays, and dynamically allocated data structures. +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]`. -All segments except the Program Segment have dynamic address spaces. The Program Segment has a fixed size during execution. +![Range Check builtin segment with valid values](range-check-builtin-valid.png) -### Segment Layout +#### Out-of-Range Error Example -The segments are ordered in memory as follows: +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. -1. **Segment 0**: Program Segment -2. **Segment 1**: Execution Segment -3. **Segment 2 to x**: Builtin Segments (dynamic) -4. **Segment x + 1 to y**: User Segments (dynamic) +![Range Check error: Value exceeds maximum range](range-check-builtin-error1.png) -The number of builtin and user segments varies depending on the program. +#### Invalid Type Error Example -# Relocation Example +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. -The following Cairo Zero program demonstrates segment definition and relocation: +![Range Check error: Value is a relocatable address](range-check-builtin-error2.png) -```cairo -%builtins output +## Bitwise Builtin -func main(output_ptr: felt*) -> (output_ptr: felt*) { +The Bitwise Builtin facilitates bitwise operations—AND (`&`), XOR (`^`), and OR (`|`)—on field elements. It supports tasks requiring bit-level manipulation. - // We are allocating three different values to segment 1. - [ap] = 10, ap++; - [ap] = 100, ap++; - [ap] = [ap - 2] + [ap - 1], ap++; +### How It Works - // 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++; +The Bitwise builtin uses a dedicated memory segment. Each operation involves a block of 5 cells: - // Asserts that output_ptr equals to 110. - assert [output_ptr] = 110; +| 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 | - // Returns the output_ptr + 1 as the next unused memory address. - return (output_ptr=output_ptr + 1); -} -``` +For example, if `x = 5` (binary `101`) and `y = 3` (binary `011`): -This example shows values being allocated to Segment 1 using the `ap` (allocation pointer). The `output_ptr` is set to a memory address, and an assertion verifies its value. Finally, the updated `output_ptr` is returned. At the end of execution, these relocatable values are converted into a contiguous memory space. +- `5 & 3 = 1` (binary `001`) +- `5 ^ 3 = 6` (binary `110`) +- `5 | 3 = 7` (binary `111`) + +### Example Usage -Cairo VM Execution and Performance +This Cairo function demonstrates the use of the Bitwise Builtin: -# Cairo VM Execution and Performance +```cairo +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin -The state of the Cairo VM at any step `i` is defined by the tuple `(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 instruction fetched from memory, mirroring a CPU's fetch-decode-execute cycle. +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); +} +``` -Each step of the execution is an atomic process that checks one instruction and enforces its semantics as algebraic constraints within the Cairo AIR. For instance, an instruction might load values from memory, perform an operation (add, multiply), write the result to memory, and update registers like `pc` and `ap`. +## EC OP Builtin -These transition rules are deterministic. If at any point the constraints are not satisfied (e.g., an illegal state transition), the execution cannot be proven. +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. -## Builtins +### Cells Organization -Builtins are predefined, optimized low-level execution units embedded within the Cairo architecture. They significantly enhance performance compared to implementing the same logic using Cairo's instruction set. +The EC OP builtin uses a dedicated memory segment. Each operation is defined by a block of 7 cells: -Cairo VM CPU and Instructions +| 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 | -# Cairo VM CPU and Instructions +The first five cells are inputs provided by the program, and the last two cells are outputs computed by the VM upon reading. -The Cairo VM CPU architecture dictates instruction processing and state changes, mirroring a physical CPU. It operates on a Von Neumann architecture, with instructions and data sharing the same memory space. The execution follows a **fetch-decode-execute cycle**. +#### Valid Operation Example -## Registers +This example shows a correctly configured EC OP builtin operation with all input values set. -Registers are high-speed storage locations crucial for immediate data processing. The Cairo VM's state is defined by three registers: +![EC OP builtin segment with complete input values](ecop-segment.png) -- **`pc` (Program Counter)**: Stores the memory address of the next instruction. It typically increments after each instruction but can be modified by jump or call instructions. -- **`ap` (Allocation Pointer)**: Acts as a stack pointer, usually indicating the next available memory cell. Instructions often increment `ap` by 1. -- **`fp` (Frame Pointer)**: Provides a fixed reference for the current function's stack frame, allowing access to arguments and return addresses at stable negative offsets from `fp`. `fp` is set to the current `ap` value upon function calls. +When the program reads cells at offsets 5 and 6, the VM computes `R = P + mQ` and returns the resulting coordinates. -Cairo VM Builtins +#### Error Condition Example -Introduction to Cairo VM Builtins +This example illustrates an error condition where the program attempts to read the output cells with incomplete inputs. -# Introduction to Cairo VM Builtins +![Incomplete input values in EC OP builtin segment](ecop-invalid-inputs.png) -Builtins in the Cairo VM are analogous to Ethereum precompiles, representing primitive operations implemented in the client's language rather than EVM opcodes. The Cairo architecture allows for flexible addition or removal of builtins, leading to different layouts. Builtins add constraints to the CPU AIR, which can increase verification time. +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. -## How Builtins Work +## Keccak Builtin -A builtin enforces constraints on Cairo memory to perform specific tasks, such as computing a hash. Each builtin operates on a dedicated memory segment, accessible via memory-mapped I/O, where specific memory address ranges are dedicated to builtins. Interaction with a builtin occurs by reading or writing to these corresponding memory cells. +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. -Builtin constraints are categorized into two main types: "validation property" and "deduction property." Builtins with a deduction property are typically split into blocks of cells, where some cells are constrained by a validation property. If a defined property does not hold, the Cairo VM will panic. +### Cells Organization -### Validation Property +The Keccak builtin uses a dedicated memory segment organized in blocks of 16 cells: -A validation property defines the constraints a value must satisfy before being written to a builtin's memory cell. For instance, the Range Check builtin only accepts felts within the range `[0, 2**128)`. Writing a value to the Range Check builtin is only permitted if these constraints are met. +| 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 | -## Builtins List +The builtin processes each block independently with the following rules: -The Cairo VM implements several builtins, each with a specific purpose. The following table outlines these builtins: +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. -| Builtin | Description | -| ----------- | ------------------------------------------------------------------------------------------------------- | -| Output | Stores public memory required for STARK proof generation (input/output values, builtin pointers, etc.). | -| Pedersen | Computes the Pedersen hash `h` of two felts `a` and `b` (`h = Pedersen(a, b)`). | -| Range Check | Verifies that a felt `x` is within the bounds `[0, 2**128)`. | -| ECDSA | Verifies ECDSA signatures for a given public key and message. Primarily used by Cairo Zero. | +#### Example Operation -Hashing Builtins +![Keccak builtin segment with a complete operation](keccak-segment.png) -# Hashing Builtins +## Mod Builtin -## Pedersen Builtin +The Mod builtin is designed for modular arithmetic operations, specifically computing `a % b` and `a // b` (integer division). It leverages the Range Check builtin to validate values and ensure they fall within specific bounds, typically `[0, 2**96)`. -The Pedersen builtin computes the Pedersen hash of two field elements (felts) efficiently within the Cairo VM. +### How It Works -### Cells Organization +The Mod builtin operates on a dedicated memory segment. For each modular arithmetic operation, it uses a block of 3 cells: -The Pedersen builtin uses a dedicated segment organized in triplets of cells: two input cells and one output cell. +| Offset | Description | Role | +| ------ | ----------- | ------ | +| 0 | `a` value | Input | +| 1 | `b` value | Input | +| 2 | `a % b` | Output | -- **Input cells**: Must store field elements (felts). Relocatable values (pointers) are not allowed. -- **Output cell**: The value is deduced from the input cells. When an output cell is read, the VM computes the Pedersen hash of the two input cells and writes the result. +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. -**Example Snapshots:** +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. -- **Valid State**: Input cells contain felts, and the output cell has been computed after being read. -- **Pending Computation**: Input cells are filled, but the output cell is empty as it hasn't been read yet. +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. -## Keccak Builtin +## Segment Arena Builtin -The Keccak builtin implements the core functionality of the SHA-3 family of hash functions, specifically the keccak-f1600 permutation, which is crucial for Ethereum compatibility. +The Segment Arena builtin enhances Cairo VM's memory management by tracking segment endpoints, simplifying memory operations involving segment allocation and finalization. ### Cells Organization -The Keccak builtin utilizes a dedicated memory segment structured in blocks of 16 consecutive cells: - -- **First 8 cells**: Store the 1600-bit input state `s`, with each cell holding 200 bits. -- **Next 8 cells**: Store the 1600-bit output state `s'`, with each cell holding 200 bits. +Each Segment Arena builtin instance manages state using blocks of 3 cells: -### Rules and Operations +- First cell: Base address of the info pointer. +- Second cell: Current number of allocated segments. +- Third cell: Current number of finalized segments. -1. **Input Validation**: Each input cell must contain 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 for subsequent accesses within the same block. +This structure works in conjunction with an Info segment, also organized in blocks of 3 cells: -Cryptographic Builtins +- 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). -# Cryptographic Builtins +![Segment Arena builtin segment](segment-arena.png) -This section details the cryptographic builtins available in the Cairo VM, which are essential for performing cryptographic operations efficiently. +Cairo Compilation: Sierra and Casm -## ECDSA Builtin +# Cairo Compilation: Sierra and Casm -The ECDSA (Elliptic Curve Digital Signature Algorithm) builtin is used to verify cryptographic signatures on the STARK curve, primarily to validate that a message hash was signed by the holder of a specific private key. +## Sierra and Casm -### Memory Organization +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. -The ECDSA builtin utilizes a dedicated memory segment and a signature dictionary: +## Why Casm is Needed -1. **Memory Segment**: Stores public keys and message hashes as field elements. - - **Cell Layout**: - - Even offsets (`2n`): Store public keys. - - Odd offsets (`2n+1`): Store message hashes. - - A public key at offset `2n` pairs with a message hash at offset `2n+1`. -2. **Signature Dictionary**: Maps public key offsets to their corresponding signatures. +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. -### Signature Verification Process +## Safe Casm -Signatures must be registered in the signature dictionary before use. The VM verifies signatures when values are written to the ECDSA segment: +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. -- Writing a public key at offset `2n` checks if it matches the signature's key. -- Writing a message hash at offset `2n+1` verifies it against the signed hash. -- Failures result in immediate VM errors. +## Compilation of Loops and Recursion -## EC OP Builtin +In Cairo, loops and recursion are compiled into similar low-level representations. Compiling examples to Sierra reveals that loops are often translated into recursive functions within the Sierra statements. To observe this, one can enable `sierra-text = true` in `Scarb.toml` and run `scarb build`. -The 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. +## Sierra Code Structure -### Cells Organization +Sierra files are structured into three main parts: -Each EC OP operation uses a block of 7 cells: +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. -- **Input Cells (Offsets 0-4)**: - - `0`: P.x coordinate - - `1`: P.y coordinate - - `2`: Q.x coordinate - - `3`: Q.y coordinate - - `4`: m scalar value -- **Output Cells (Offsets 5-6)**: - - `5`: R.x coordinate - - `6`: R.y coordinate +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. -The VM computes the output coordinates when the output cells are read, provided all input cells contain valid field elements. Incomplete or invalid input values will cause the builtin to fail. +### Example: Inlining in Sierra -## Keccak Builtin +Consider a program with inlined and non-inlined functions. The Sierra code shows how function calls are represented. For example, `function_call()` is used to execute a non-inlined function. The execution then proceeds through `felt252_const`, `store_temp`, and `felt252_add` libfuncs. Inlined code might use different variable IDs due to prior assignments. The `return` instruction of called functions is often omitted in favor of integrating their results into the calling function's logic. -The Keccak builtin computes the Keccak-256 hash of a given input. +#### Casm Code Example -### Syntax +Here's a corresponding Casm code snippet for the described program: ```cairo,noplayground -pub extern fn keccak_syscall( - input: Span, -) -> SyscallResult implicits(GasBuiltin, System) nopanic; +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 ``` -### Description +This Casm code involves instructions like `call rel`, `ret`, and memory operations (`[ap + offset] = value, ap++`). -Computes the Keccak-256 hash of a `Span` input and returns the hash as a `u256`. +Security and Provability in Cairo -### Error Conditions +### Security and Provability in Cairo -The Keccak builtin throws an error if: +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. -- Any input cell value exceeds 200 bits (≥ 2^200). -- Any input cell contains a relocatable value (pointer). -- An output cell is read before all eight input cells are initialized. +#### Provability and Malicious Provers -## Poseidon Builtin +- **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. -The Poseidon builtin is a hash function optimized for zero-knowledge proof systems, offering a balance between security and efficiency. +#### Gas Metering Complications -Arithmetic and Bitwise Builtins +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. -# Arithmetic and Bitwise Builtins +To address this, the plan is to require users to have sufficient gas for the unhappy flow before initiating operations like `find_element`. -## Bitwise Builtin +#### Hints in Cairo -The Bitwise Builtin in the Cairo VM supports bitwise operations: AND (`&`), XOR (`^`), and OR (`|`) on field elements. It operates on a dedicated memory segment using a 5-cell block for each operation: input `x`, input `y`, output `x & y`, output `x ^ y`, and output `x | y`. +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. -### Example Usage +Cairo Runner and Proof Generation -```cairo -from starkware.cairo.common.cairo_builtins import BitwiseBuiltin +# Cairo Runner and Proof Generation -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); -} -``` +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. -## Arithmetic Builtins (AddMod, MulMod) +## Runner Modes -The `AddMod` and `MulMod` builtins support modular arithmetic operations. They work with `UInt384` types, which are represented as four 96-bit words, aligning with the `range_check96` builtin. +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. -### AddMod +### Execution Mode -`AddMod` computes modular addition `c ≡ a + b mod(p)`. It has a limited quotient `k` (typically 0 or 1) because the sum of two numbers near the modulus `p` does not exceed `2p - 2`. +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. -### MulMod +### Proof Mode -`MulMod` computes modular multiplication `c ≡ a * b mod(p)`. It supports a higher quotient bound (up to `2^384`) to handle potentially large products. It uses the extended GCD algorithm for deduction, flagging `ZeroDivisor` errors if `b` and `p` are not coprime. +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. -### Example Usage (AddMod) +Advanced Topics and Utilities -```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 +Development Tools and Utilities -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(); +# Development Tools and Utilities - // 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); +This section covers useful development tools provided by the Cairo project, including automatic formatting, quick warning fixes, a linter, and IDE integration. - // 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; +## Automatic Formatting with `scarb fmt` - // 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, - ); +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. - // Bump the range_check96_ptr forward: 8 input words + 4 output words = 12 total - let range_check96_ptr = range_check96_ptr + 12; +To format a Cairo project, navigate to the project directory and run: - // Return a pointer to the result, sitting in the last 4 words - return cast(range_check96_ptr - 4, UInt384*); +```bash +scarb fmt +``` - // 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 +Code sections that should not be formatted can be marked with `#[cairofmt::skip]`: - // No offsets needed for MulMod here - mul_offsets: -} +```cairo, noplayground +#[cairofmt::skip] +let table: Array = array![ + "oxo", + "xox", + "oxo", +]; ``` -Memory and Output Builtins +## IDE Integration Using `cairo-language-server` -# Memory and Output Builtins +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). -The **Output Builtin** in the Cairo VM manages the output segment of memory using the `output_ptr`. It acts as a bridge to the external world through public memory, enabling verifiable outputs. +Cryptography and Zero-Knowledge Proofs -## Memory Organization +# Cryptography and Zero-Knowledge Proofs -The output segment is a contiguous block of cells, starting at a base address. All cells within this segment are public and can be written to and read from without specific constraints. The segment grows as the program writes values. +## Hash Functions in Cairo -## Role in STARK Proofs +Cairo's core library offers two hash functions: Pedersen and Poseidon. -The Output Builtin's integration with public memory is crucial for STARK proof construction and verification: +### Pedersen Hash -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 hash the output segment to create a commitment, allowing verification without re-execution. +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). -## Implementation References +### Poseidon Hash -References for the Output Builtin implementation: +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. -- [TypeScript Output Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/output.ts#L4) -- [Python Output Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/vm/output_builtin_runner.py) +## Arithmetic Circuits in Zero-Knowledge Proof Systems -Builtin Properties, Errors, and Implementations +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. -# Builtin Properties, Errors, and Implementations +### zk-SNARKs Approach -## Pedersen Builtin +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. -### Errors +### zk-STARKs Approach -1. **Missing Input Data**: Reading cell 3:2 throws an error if an input cell (e.g., 3:0) is empty, as the VM cannot compute a hash without complete input. -2. **Relocatable Values**: Reading cell 3:5 throws an error if an input cell (e.g., 3:4) contains a relocatable value (memory address), as the Pedersen builtin only accepts field elements. +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. -These errors manifest when the output cell is read. A more robust implementation could validate input cells upon writing to reject relocatable values immediately. +## Implementing Arithmetic Circuits in Cairo -### Implementation References +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\). -- [TypeScript Pedersen Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/pedersen.ts#L4) -- [Python Pedersen Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/builtins/hash/hash_builtin_runner.py) -- [Rust Pedersen Builtin](https://github.com/lambdaclass/cairo-vm/blob/41476335884bf600b62995f0c005be7d384eaec5/vm/src/vm/runners/builtin_runner/hash.rs) -- [Go Pedersen Builtin](https://github.com/NethermindEth/cairo-vm-go/blob/dc02d614497f5e59818313e02d2d2f321941cbfa/pkg/vm/builtins/pedersen.go) -- [Zig Pedersen Builtin](https://github.com/keep-starknet-strange/ziggy-starkdust/blob/55d83e61968336f6be93486d7acf8530ba868d7e/src/vm/builtins/builtin_runner/hash.zig) +### Basic Arithmetic Gates -## Range Check Builtin +- `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`. + +### Example Circuit: `a * (a + b)` -### Properties +The following code demonstrates the creation and evaluation of a circuit that computes \(a \cdot (a + b)\\) over the BN254 prime field: -- **Validation Timing**: Validates values immediately upon cell write, unlike builtins with deduction properties. +```cairo, noplayground +use core::circuit::{ + AddInputResultTrait, CircuitElement, CircuitInput, CircuitInputs, CircuitModulus, + CircuitOutputsTrait, EvalCircuitTrait, circuit_add, circuit_mul, u384, +}; -### Valid Operation Example +// 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::> {}; -- Writes `0`, `256`, and `2^128-1` to the Range Check segment, all within the permitted range `[0, 2^128-1]`. + let add = circuit_add(a, b); + let mul = circuit_mul(a, add); -### Errors + let output = (mul,); -1. **Out-of-Range Error**: Occurs when attempting to write a value exceeding the maximum allowed (`2^128`). -2. **Invalid Type Error**: Occurs when attempting to write a relocatable address (memory pointer) instead of a field element. + let mut inputs = output.new_inputs(); + inputs = inputs.next([10, 0, 0, 0]); + inputs = inputs.next([20, 0, 0, 0]); -### Implementation References + let instance = inputs.done(); -- [TypeScript Signature Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/ecdsa.ts) -- [Python Signature Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/builtins/signature/signature_builtin_runner.py) -- [Rust Signature Builtin](https://github.com/lambdaclass/cairo-vm/blob/41476335884bf600b62995f0c005be7d384eaec5/vm/src/vm/runners/builtin_runner/signature.rs) -- [Go Signature Builtin](https://github.com/NethermindEth/cairo-vm-go/blob/dc02d614497f5e59818313e02d2d2f321941cbfa/pkg/vm/builtins/ecdsa.go) -- [Zig Signature Builtin](https://github.com/keep-starknet-strange/ziggy-starkdust/blob/55d83e61968336f6be93486d7acf8530ba868d7e/src/vm/builtins/builtin_runner/signature.zig) + let bn254_modulus = TryInto::< + _, CircuitModulus, + >::try_into([0x6871ca8d3c208c16d87cfd47, 0xb85045b68181585d97816a91, 0x30644e72e131a029, 0x0]) + .unwrap(); -## ECDSA Signature Builtin + let res = instance.eval(bn254_modulus).unwrap(); -### Errors + let add_output = res.get_output(add); + let circuit_output = res.get_output(mul); -1. **Hash Mismatch**: An error occurs if the hash written at an offset does not match the hash originally signed with a given public key. -2. **Invalid Public Key**: An error occurs if the public key written at an offset does not match the public key used to create the signature. + 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"); -### Implementation References + (add_output, circuit_output) +} -- [TypeScript Signature Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/ecdsa.ts) -- [Python Signature Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/builtins/signature/signature_builtin_runner.py) -- [Rust Signature Builtin](https://github.com/lambdaclass/cairo-vm/blob/41476335884bf600b62995f0c005be7d384eaec5/vm/src/vm/runners/builtin_runner/signature.rs) -- [Go Signature Builtin](https://github.com/NethermindEth/cairo-vm-go/blob/dc02d614497f5e59818313e02d2d2f321941cbfa/pkg/vm/builtins/ecdsa.go) -- [Zig Signature Builtin](https://github.com/keep-starknet-strange/ziggy-starkdust/blob/55d83e61968336f6be93486d7acf8530ba868d7e/src/vm/builtins/builtin_runner/signature.zig) +#[executable] +fn main() { + eval_circuit(); +} +``` -## Poseidon Builtin +The process involves defining inputs, describing the circuit, specifying outputs, assigning values, defining the modulus, evaluating the circuit, and retrieving output values. -### Hashing Examples +## Modular Arithmetic Builtin -- **Single Value Hashing**: Takes an initial state (e.g., `(42, 0, 0)`), applies padding `(43, 0, 0)`, computes the Hades permutation, and stores the result in an output cell. The first component of the result is the hash output. -- **Sequence Hashing**: For inputs `(73, 91)`, the VM takes the state `(73, 91, 0)`, applies padding `(73, 91+1, 0)`, computes the Hades permutation, and stores all three resulting components in output cells. These can be used for further computation or chaining. +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. -### Error Condition +### Structure and Operation -- **Relocatable Value Input**: An error occurs when trying to write a relocatable value (memory address) to an input cell, as the Poseidon builtin only operates on field elements. Input validation happens upon reading the output. +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`. -### Implementation References +### Modular Addition Example -- [TypeScript Poseidon Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/poseidon.ts) -- [Python Poseidon Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/builtins/poseidon/poseidon_builtin_runner.py) +The `AddMod` builtin can be used to compute `x + y (mod p)` for `UInt384` values, as illustrated in Cairo Zero code. -## Mod Builtin (AddMod, MulMod) +Oracles -### Operation Example (AddMod) +# Oracles -- Takes `UInt384` values `x`, `y`, and modulus `p`. -- Writes `x` and `y` to the values table. -- Uses offsets `[0, 4, 8]` to point to `x`, `y`, and the result `c`. -- `run_mod_p_circuit` computes `x + y (mod p)` and stores the result at offset 8. -- Example: `p = 5`, `x = 3`, `y = 4`. Values table `[3, 4, 2]`. `3 + 4 = 7`, `7 mod 5 = 2`, matching `c`. +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. -### Errors +> 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. -- **Missing Operand**: If `x` is missing a word. -- **Zero Divisor**: If `b` and `p` are not coprime for `MulMod` and `a` is unknown. -- **Range Check Failure**: If any word exceeds `2^96`. +## Why use Oracles? -## Segment Arena Builtin +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`). -### Segment Arena States +## What We’ll Build -- **Valid State (Snapshot 1)**: Demonstrates dictionary allocation where `info_ptr` points to a new info segment, `n_dicts` increments, the info segment grows, and a new dictionary segment `<3:0>` is allocated. -- **Valid State (Snapshot 2)**: Shows allocation of another dictionary, info segment growth, squashed dictionaries with end addresses set, sequential squashing indices, and unfinished dictionaries with `0` end addresses. +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. -### Error Conditions +The complete example is located in the `listing_oracles/` directory. -1. **Invalid State (Non-relocatable `info_ptr`)**: Occurs when `info_ptr` contains a non-relocatable value (e.g., `ABC`), triggering an error upon accessing the info segment. -2. **Inconsistent State**: Occurs when `n_squashed` is greater than `n_segments`. +## The Cairo Package -### Key Validation Rules +The `Scarb.toml` manifest declares an executable package, depends on `cairo_execute` for running with Scarb, and includes the `oracle` crate for `oracle::invoke`. -- 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. +Filename: listing_oracles/Scarb.toml + +```toml +[package] +name = "example" +version = "0.1.0" +edition = "2024_07" +publish = false -### Implementation References +[dependencies] +cairo_execute = "2.12.0" +oracle = "0.1.0-dev.4" -- [TypeScript Segment Arena Builtin](https://github.com/kkrt-labs/cairo-vm-ts/blob/58fd07d81cff4a4bb45c30ab99976ba66f0576ad/src/builtins/segment_arena.ts) -- [Python Segment Arena Builtin](https://github.com/starkware-libs/cairo-lang/blob/0e4dab8a6065d80d1c726394f5d9d23cb451706a/src/starkware/cairo/lang/builtins/segment_arena/segment_arena_builtin_runner.py) -- [Rust Segment Arena Builtin](https://github.com/lambdaclass/cairo-vm/blob/41476335884bf600b62995f0c005be7d384eaec5/vm/src/vm/runners/builtin_runner/segment_arena.rs) -- [Go Segment Arena Builtin](https://github.com/NethermindEth/cairo-vm-go/blob/dc02d614497f5e59818313e02d2d2f321941cbfa/pkg/vm/builtins/segment_arena.go) -- [Zig Segment Arena Builtin](https://github.com/keep-starknet-strange/ziggy-starkdust/blob/55d83e61968336f6be93486d7acf8530ba868d7e/src/vm/builtins/builtin_runner/segment_arena.zig) +[executable] -Hints and the Cairo Runner +[cairo] +enable-gas = false -# Hints and the Cairo Runner +[dev-dependencies] +cairo_test = "2" +``` -Cairo supports nondeterministic programming through "hints," which allow the prover to set memory values. This mechanism accelerates operations that are cheaper to verify than to execute, such as complex arithmetic, by having the prover compute the result and constraining it. Hints are not part of the proved trace, making their execution "free" from the verifier's perspective. However, constraints are crucial to ensure the prover's honesty and prevent security issues from underconstrained programs. +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. -## Hints in Cairo +Filename: listing_oracles/src/lib.cairo -Smart contracts written in Cairo cannot contain user-defined hints. The hints used are determined by the Sierra to Casm compiler, which ensures only "safe" Casm is generated. While future native Cairo might support hints, they will not be available in Starknet smart contracts. +```cairo +use core::num::traits::Pow; -Security considerations arise with hints, particularly concerning gas metering. If a user lacks sufficient gas for an "unhappy flow" (e.g., searching for an element that isn't present), a malicious prover could exploit this to lie about the outcome. The proposed solution is to require users to have enough gas for the unhappy flow before execution. +// 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,)) +} -## The Cairo Runner +// Call into the Rust oracle to convert an integer to little-endian bytes. +fn to_le_bytes(val: u64) -> oracle::Result> { + oracle::invoke( + "stdio:cargo -q run --manifest-path ./src/my_oracle/Cargo.toml", 'to_le_bytes', (val,), + ) +} -The Cairo Runner orchestrates the execution of compiled Cairo programs, implementing the Cairo machine's memory, execution, builtins, and hints. It is written in Rust by LambdaClass and is available as a standalone binary or library. +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}"); -### Runner Modes + 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()); -The Cairo Runner operates in two primary modes: + Ok(()) +} -- **Execution Mode:** This mode executes the program to completion, including hints and VM state transitions. It's useful for debugging and testing logic without the overhead of proof generation. The output includes the execution trace, memory state, and register states. The runner halts if any hint or instruction check fails. -- **Proof Mode:** This mode executes the program and prepares the necessary inputs for proof generation. It records the VM state at each step to build the execution trace and final memory. After execution, the memory dump and sequential register states can be extracted to form the execution trace for proof generation. +#[executable] +fn main(x: u64) -> bool { + match oracle_calls(x) { + Ok(()) => true, + Err(e) => panic!("Oracle call failed: {e:?}"), + } +} +``` -Cairo Language Features and Resources +### Key Concepts in Cairo Oracle Interaction -Cairo Language Features and Attributes +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. -# Cairo Language Features and Attributes +## The Rust Oracle -Cairo provides several attributes that offer hints to the compiler or enable specific functionalities: +The Rust side implements the oracle endpoints. The `cairo_oracle_server` crate handles input decoding and output encoding back to Cairo. -| Attribute | Description | -| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | -| `#[inline(never)]` | Hints to the compiler to never inline the annotated function. | -| `#[must_use]` | Hints to the compiler that the return value of a function or a specific returned type must be used. | -| `#[generate_trait]` | Automatically generates a trait for an `impl`. | -| `#[available_gas(...)]` | Sets the maximum amount of gas available to execute a function. | -| `#[panic_with('...', wrapper_name)]` | Creates a wrapper for the annotated function that panics with the given data if the function returns `None` or `Err`. | -| `#[test]` | Marks a function as a test function. | -| `#[cfg(...)]` | A configuration attribute, commonly used to configure a `tests` module with `#[cfg(test)]`. | -| `#[should_panic]` | Specifies that a test function should necessarily panic. | +Filename: listing_oracles/src/my_oracle/Cargo.toml -## Hashing with `Hash` +```toml +[package] +name = "my_oracle" +version = "0.1.0" +edition = "2021" +publish = false -The `Hash` trait can be derived on structs and enums, allowing them to be hashed easily. For a type to derive `Hash`, all its fields or variants must also be hashable. More information is available in the [Hashes section](ch12-04-hash.md). +[dependencies] +anyhow = "1" +cairo-oracle-server = "0.1" +starknet-core = "0.11" +``` -## Starknet Storage with `starknet::Store` +Filename: listing_oracles/src/my_oracle/src/main.rs -Relevant for Starknet development, the `starknet::Store` trait enables a type to be used in smart contract storage by automatically implementing necessary read and write functions. Detailed information can be found in the [Contract storage section](ch101-01-00-contract-storage.md). +```rust, noplayground +use anyhow::ensure; +use cairo_oracle_server::Oracle; +use std::process::ExitCode; + +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() +} +``` -## Implementing Arithmetic Circuits in Cairo +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`. -Cairo's circuit constructs are available in the `core::circuit` module. Arithmetic circuits utilize builtins like `AddMod` and `MulMod` for operations modulo a prime `p`. This enables the creation of basic arithmetic gates: `AddModGate`, `SubModGate`, `MulModGate`, and `InvModGate`. +## Running the Example -An example of a circuit computing \\(a \cdot (a + b)\\\) over the BN254 prime field is provided: +To run the example, navigate to the example directory and execute the following command: -```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(); -# } +```bash +scarb execute --experimental-oracles --print-program-output --arguments 25000000 ``` -## Cairo Prelude +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. -The Cairo prelude is a collection of commonly used modules, functions, data types, and traits that are automatically available in every Cairo module. It includes primitive data types (integers, bools, arrays, dicts), traits for operations (arithmetic, comparison, serialization), operators, and utility functions for common tasks. The prelude is defined in the `lib.cairo` file of the corelib crate. +To observe an error, try a non-perfect square like `27`: -Cairo's Core Libraries and Data Handling +```bash +scarb execute --experimental-oracles --print-program-output --arguments 27 +``` -# Cairo's Core Libraries and Data Handling +The `sqrt` endpoint will return an error, which propagates to Cairo, causing the program to panic. -Cairo Editions and Prelude Management +## Summary -# Cairo Editions and Prelude Management +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 provides fundamental programming constructs and operations for Cairo programs, making them available without explicit imports. This enhances developer experience by preventing repetition. +Appendices and References -## Prelude Versions and Editions +# Appendices and References -You can select the prelude version by specifying the edition in your `Scarb.toml` file. For example, `edition = "2024_07"` loads the prelude from July 2024. New projects created with `scarb new` automatically include `edition = "2024_07"`. Different prelude versions expose different functions and traits, so specifying the correct edition is crucial. It's generally recommended to use the latest edition for new projects and migrate to newer editions as they become available. +## Appendix A - Keywords -### Available 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. -| 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) | +### Strict Keywords -Sierra: The Intermediate Layer for Provability +These keywords can only be used in their correct contexts and cannot be used as names for items. -# Sierra: The Intermediate Layer for Provability +- `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 -What is proven is the correct Casm execution, regardless of what the user sends to the Starknet sequencer. This necessitates a Sierra -> Casm compiler to translate user code into Casm. +## Appendix B - Syntax -## Why Sierra is Needed +This section details the usage of specific syntax elements in Cairo. -Sierra serves as an additional layer between user code and the provable code (Casm) to address limitations in Cairo and requirements for decentralized L2s. +### Tuples -### Reverted Transactions, Unsatisfiable AIRs, and DoS Attacks +Tuples are used for grouping multiple values. -- **Sequencer Compensation:** Sequencers must be compensated for work done, even on reverted transactions. Sending transactions that fail after extensive computation is a DoS attack if the sequencer cannot charge for the work. -- **Provability Limitations:** Sequencers cannot determine if a transaction will fail without executing it (similar to solving the halting problem). -- **Validity Rollups:** Including failed transactions, as done in Ethereum, is not straightforward in validity rollups. -- **Cairo Zero Issues:** Without a separating layer, users could write unprovable code (e.g., `assert 0=1`). Such code translates to unsatisfiable polynomial constraints, halting any Casm execution containing it and preventing proof generation. +| 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. | -AIR: Enabling Program Proofs +### Curly Braces -# AIR: Enabling Program Proofs +Curly braces `{}` have specific contexts in Cairo. -AIR stands for **Arithmetic Intermediate Representation**. It is an arithmetization technique that converts a computational statement into a set of polynomial equations, which form the basis of proof systems like STARKs. +| Context | Explanation | +| :----------- | :--------------- | +| `{...}` | Block expression | +| `Type {...}` | `struct` literal | -## AIR Inputs and Proof Generation +## Appendix C - Derivable Traits -The AIR's private input consists of the **execution trace** and the **memory**. The public input includes the **initial and final states**, **public memory**, and configuration data. The prover uses these inputs to generate a proof, which the verifier can then check asynchronously. +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. -## AIRs in Cairo +### Hashing with `Hash` -Cairo utilizes a set of AIRs to represent the **Cairo machine**, a Turing-complete machine for the Cairo ISA. This allows for the proving of arbitrary code executed on the Cairo machine. Each component of the Cairo machine, such as the CPU, Memory, and Builtins, has a corresponding AIR. Writing efficient AIRs is crucial for the performance of proof generation and verification. +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. -Applications of Cairo +### Starknet Storage with `starknet::Store` -# Applications of Cairo +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. -Cairo Whitepaper Summary +## Appendix D - The Cairo Prelude -# Cairo Whitepaper Summary +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. -## The Cairo whitepaper +The core library prelude is defined in the `lib.cairo` file of the corelib crate. It includes: -The original paper introducing Cairo by StarkWare explains Cairo as a language for writing provable programs, details its architecture, and shows how it enables scalable, verifiable computation without relying on trusted setups. You can find the paper at [https://eprint.iacr.org/2021/1063.pdf](https://eprint.iacr.org/2021/1063.pdf). +- **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. diff --git a/python/uv.lock b/python/uv.lock index 54646b0e..ad92887a 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -380,9 +380,6 @@ dependencies = [ { name = "tenacity" }, { name = "toml" }, { name = "tqdm" }, - { name = "typer" }, - { name = "uvicorn", extra = ["standard"] }, - { name = "websockets" }, ] [package.optional-dependencies] @@ -451,10 +448,7 @@ requires-dist = [ { name = "testcontainers", extras = ["postgres"], marker = "extra == 'dev'", specifier = ">=4.0.0" }, { name = "toml", specifier = ">=0.10.2" }, { name = "tqdm", specifier = ">=4.66.0" }, - { name = "typer", specifier = ">=0.15.0" }, { name = "types-toml", marker = "extra == 'dev'", specifier = ">=0.10.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.36.0" }, - { name = "websockets", specifier = ">=13.0" }, ] provides-extras = ["dev"] @@ -1005,15 +999,15 @@ wheels = [ [[package]] name = "databricks-sdk" -version = "0.66.0" +version = "0.67.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/c0/11a97a60a2e2d8d413845d5b7c146a3a83df5e3e37b614169f7bb5f89ca6/databricks_sdk-0.66.0.tar.gz", hash = "sha256:0168e0ffdee8a11ee527e6de888ce0f6ed587329cf0c84038fb215a8b732600f", size = 759829, upload-time = "2025-09-22T13:07:25.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/5b/df3e5424d833e4f3f9b42c409ef8b513e468c9cdf06c2a9935c6cbc4d128/databricks_sdk-0.67.0.tar.gz", hash = "sha256:f923227babcaad428b0c2eede2755ebe9deb996e2c8654f179eb37f486b37a36", size = 761000, upload-time = "2025-09-25T13:32:10.858Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/2b/579c272505a3b98cbabad80b517574607022219b3207c65b09c8222d8ac7/databricks_sdk-0.66.0-py3-none-any.whl", hash = "sha256:be4d7e48a682fc4cb8dc51730e660492054e143c7831c8f5485025cc72b0c765", size = 717483, upload-time = "2025-09-22T13:07:23.008Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ca/2aff3817041483fb8e4f75a74a36ff4ca3a826e276becd1179a591b6348f/databricks_sdk-0.67.0-py3-none-any.whl", hash = "sha256:ef49e49db45ed12c015a32a6f9d4ba395850f25bb3dcffdcaf31a5167fe03ee2", size = 718422, upload-time = "2025-09-25T13:32:09.011Z" }, ] [[package]] @@ -1461,7 +1455,7 @@ grpc = [ [[package]] name = "google-api-python-client" -version = "2.182.0" +version = "2.183.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -1470,9 +1464,9 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/cb/b85b1d7d7fd520739fb70c4878f1f414043c3c34434bc90ba9d4f93366ed/google_api_python_client-2.182.0.tar.gz", hash = "sha256:cb2aa127e33c3a31e89a06f39cf9de982db90a98dee020911b21013afafad35f", size = 13599318, upload-time = "2025-09-16T21:10:57.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/1f/49a2c83fc6dcd8b127cc9efbecf7d5fc36109c2028ba22ed6cb4d072fca4/google_api_python_client-2.183.0.tar.gz", hash = "sha256:abae37e04fecf719388e5c02f707ed9cdf952f10b217c79a3e76c636762e3ea9", size = 13645623, upload-time = "2025-09-23T22:27:00.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/29/76dabe97ebb710ca9a308f0415b2206e37d149983ec2becbf66525c52322/google_api_python_client-2.182.0-py3-none-any.whl", hash = "sha256:a9b071036d41a17991d8fbf27bedb61f2888a39ae5696cb5a326bf999b2d5209", size = 14168745, upload-time = "2025-09-16T21:10:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ab/06/1974f937172854bc7622eff5c2390f33542ceb843f305922922c8f5f7f17/google_api_python_client-2.183.0-py3-none-any.whl", hash = "sha256:2005b6e86c27be1db1a43f43e047a0f8e004159f3cceddecb08cf1624bddba31", size = 14214837, upload-time = "2025-09-23T22:26:57.758Z" }, ] [[package]] @@ -1745,42 +1739,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/a2/0d269db0f6163be503775dc8b6a6fa15820cc9fdc866f6ba608d86b721f2/httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24", size = 91148, upload-time = "2025-09-11T12:16:01.803Z" }, ] -[[package]] -name = "httptools" -version = "0.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780, upload-time = "2024-10-16T19:44:06.882Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297, upload-time = "2024-10-16T19:44:08.129Z" }, - { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130, upload-time = "2024-10-16T19:44:09.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148, upload-time = "2024-10-16T19:44:11.539Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949, upload-time = "2024-10-16T19:44:13.388Z" }, - { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591, upload-time = "2024-10-16T19:44:15.258Z" }, - { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344, upload-time = "2024-10-16T19:44:16.54Z" }, - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -2160,7 +2118,7 @@ wheels = [ [[package]] name = "langsmith" -version = "0.4.30" +version = "0.4.31" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -2171,9 +2129,9 @@ dependencies = [ { name = "requests-toolbelt" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/d5/4cc88f246ce615a518a715cd2bf40001d1678ad6805a3706a90570adca8f/langsmith-0.4.30.tar.gz", hash = "sha256:388fe1060aca6507be41f417c7d4168a92dffe27f28bb6ef8a1bfee4a59f3681", size = 958857, upload-time = "2025-09-22T19:05:14.156Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/f5/edbdf89a162ee025348b3b2080fb3b88f4a1040a5a186f32d34aca913994/langsmith-0.4.31.tar.gz", hash = "sha256:5fb3729e22bd9a225391936cb9d1080322e6c375bb776514af06b56d6c46ed3e", size = 959698, upload-time = "2025-09-25T04:18:19.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/d1/b2b2ea7b443c6b028aca209d2e653256912906900cc146e64c65201211b7/langsmith-0.4.30-py3-none-any.whl", hash = "sha256:110767eb83e6da2cc99cfc61958631b5c36624758b52e7af35ec5550ad846cb3", size = 386300, upload-time = "2025-09-22T19:05:11.819Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/e7a43d907a147e1f87eebdd6737483f9feba52a5d4b20f69d0bd6f2fa22f/langsmith-0.4.31-py3-none-any.whl", hash = "sha256:64f340bdead21defe5f4a6ca330c11073e35444989169f669508edf45a19025f", size = 386347, upload-time = "2025-09-25T04:18:16.69Z" }, ] [[package]] @@ -2223,7 +2181,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.77.3" +version = "1.77.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -2240,107 +2198,105 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/86/8bfd372d3d437b773b4b81d6da35674a569c10a9b805409257790e3af271/litellm-1.77.3.tar.gz", hash = "sha256:d8f9d674ef4e7673b1af02428fde27de5a8e84ca7268f003902340586aac7d96", size = 10314535, upload-time = "2025-09-21T00:59:09.655Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/b7/0d3c6dbcff3064238d123f90ae96764a85352f3f5caab6695a55007fd019/litellm-1.77.4.tar.gz", hash = "sha256:ce652e10ecf5b36767bfdf58e53b2802e22c3de383b03554e6ee1a4a66fa743d", size = 10330773, upload-time = "2025-09-24T17:52:44.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/b2/122602255b582fdcf630f8e44b5c9175391abe10be5e2f4db6a7d4173df1/litellm-1.77.3-py3-none-any.whl", hash = "sha256:f0c8c6bcfa2c9cd9e9fa0304f9a94894d252e7c74f118c37a8f2e4e525b2592b", size = 9118886, upload-time = "2025-09-21T00:59:06.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/32/90f8587818d146d604ed6eec95f96378363fda06b14817399cc68853383e/litellm-1.77.4-py3-none-any.whl", hash = "sha256:66c2bb776f1e19ceddfa977a2bbf7f05e6f26c4b1fec8b2093bd171d842701b8", size = 9138493, upload-time = "2025-09-24T17:52:40.764Z" }, ] [[package]] name = "loro" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/9e/dfdb32e85b4e3b3485162576e7d6b557b1b6450948255f5129866efccd4b/loro-1.6.0.tar.gz", hash = "sha256:f543b85c77f7366df2162de1c00e3dd2c2c75171b7e9670cffc0b6ac561d7293", size = 63849, upload-time = "2025-08-31T06:38:35.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/73/ea9e3d7b23cfe15e26b169c3d68d1ded83d05fbd691ffe0e634c725bbf1c/loro-1.6.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d30fc47cd18c9cf7dd10fed4e8c47ff1e58a03efa104e0e505bfcd70b6b5396c", size = 3098528, upload-time = "2025-08-31T06:36:16.721Z" }, - { url = "https://files.pythonhosted.org/packages/f9/33/27fa40e88c9b355ed7ae13b6feace223ccde99ca68d2037211850610d199/loro-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e43b084199ff4f3a532c8ae2679d4e0dc404a3fb0d39cb69a96f63f42757643", size = 2876822, upload-time = "2025-08-31T06:36:02.016Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c7/dcbe4005621429b28f24050ea9170c8a61b529e6af51fba18c8053842844/loro-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:647014026197d9d2e8bc1f4172059c694eb2cb33385a620eedd8f8bb7fbdc1e1", size = 3106454, upload-time = "2025-08-31T06:33:04.625Z" }, - { url = "https://files.pythonhosted.org/packages/69/7d/e8db186923571e99dc3a51a689e0aafadfb0b28da5f22d1991b07c98625e/loro-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20fe43b5f40ef5434669e81b561eceed3c37cef2340e0d119b4c48a727fc950f", size = 3180588, upload-time = "2025-08-31T06:33:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/40/a2/8a03c168735ed771ef6c3a90470e3c1e5a45fd97294d5a37b1d382619535/loro-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4957582b4ed867b02a70027b94cc71216ece9d7c32afde5f0af7ad62147b53e", size = 3548711, upload-time = "2025-08-31T06:34:07.591Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a0/e3bcc804017305944ff00938b9974d69e4bef7168dc19071d52a57452543/loro-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:690392182f7f19c235eb8f5ca28e24c4dd8c21c628e8c7a04ca94c06c425ffdc", size = 3285352, upload-time = "2025-08-31T06:34:39.414Z" }, - { url = "https://files.pythonhosted.org/packages/04/13/3923e01c9e95090c35bd2334c25a1349c2e3ee0e20741ab9c3b9ff210231/loro-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f58afca0e41854415709041d6f19398397bc3e4c4941e10e76db259d6568e32b", size = 3236075, upload-time = "2025-08-31T06:35:36.325Z" }, - { url = "https://files.pythonhosted.org/packages/ad/79/7550d0e9aadbb6732a5b8ed1022d1f6e15d69b58f142709859d6ab20faf3/loro-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ff7f24d8610855dc91db35716fd8b0c67acb4da0deb47b615a2d048b6b66f47", size = 3496193, upload-time = "2025-08-31T06:35:09.455Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/bf8a3991b61f6a1118acaa23529d9ba15a903bf9d61d767ef2e085f4af8b/loro-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:085d7da3fd14081bc8945d7e23f90b9ea00ed7352cdb77869d7e0061527a8675", size = 3256156, upload-time = "2025-08-31T06:36:31.384Z" }, - { url = "https://files.pythonhosted.org/packages/d4/2c/167dc1654f8427436b7bbc7f8a87ae62aa0cc379adc625ea46c806b0cdb0/loro-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:feb48a1dda03880fe14ee7025dd5968c449ed1436c7194b86026a173195b0f6e", size = 3442980, upload-time = "2025-08-31T06:37:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0a/047adcd7bdeef6e68d122d62e3d2c058b796a6ab7fbe282918b84d39e827/loro-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:50fccf52bb10b614952f2798622c4c83da9c1c5b48c2c1cc2b3818e477621d0b", size = 3493843, upload-time = "2025-08-31T06:37:31.815Z" }, - { url = "https://files.pythonhosted.org/packages/94/de/32e8e43fcbf81380ba2dc50fe0df73404e7fed71819801bd2cfaedf94a07/loro-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:432ce18fb3c2b2d4a609599278f798ac12f7052e66276a3d9022402b53c2b66a", size = 3401061, upload-time = "2025-08-31T06:38:03.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3a/f29583d48226398f19517a4479023fbfea29556a4372973f60e4a2d3932d/loro-1.6.0-cp310-cp310-win32.whl", hash = "sha256:f8e28569781a13f9efc7bd5dd2ea4ad67b126f5ae0a43ac02a99894adc5d1cf0", size = 2586521, upload-time = "2025-08-31T06:38:55.696Z" }, - { url = "https://files.pythonhosted.org/packages/73/80/28db767d2fb4d9c5a8e7be96eec72a66a5a97c1c536fd64bfdfa2db58c3d/loro-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:42bf24fd2935d321f1834aea4816d84e535379063aaddf8d1c877121ea4dc7ab", size = 2740626, upload-time = "2025-08-31T06:38:36.434Z" }, - { url = "https://files.pythonhosted.org/packages/f3/4e/9c968a85f81e2b5a6c7c16ee103e98893d4cc55664e915826caa5cbda6dc/loro-1.6.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e1ce4710379bd1cbfa54297e16267b7c7aacdd61f2f931594c46504e40f4c2a2", size = 3096560, upload-time = "2025-08-31T06:36:18.282Z" }, - { url = "https://files.pythonhosted.org/packages/30/8a/f830541bc58aa97ac460f48c3e6b0ed75776611ef73a04074c212cc3f8d4/loro-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b0a1a65920037856a3068024029f4677a924d61de2d2014d1cc53a679b85eb0", size = 2874562, upload-time = "2025-08-31T06:36:03.468Z" }, - { url = "https://files.pythonhosted.org/packages/21/c3/71166beb50cc50d177b49f7c191c933476731b46e2ebb73d232a507428ab/loro-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d66a4494efd3de3e6873425f77c1ad74b91d6b82864f6f0600fc84760ac313b", size = 3105694, upload-time = "2025-08-31T06:33:06.886Z" }, - { url = "https://files.pythonhosted.org/packages/71/be/dec613e14ab23bef1968795f780bd279a4158fa40751bbbee059afe31434/loro-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a597d357fc2b8875003111e9668ce9e1ce73e70a836441660f35ba37f49df373", size = 3180696, upload-time = "2025-08-31T06:33:40.345Z" }, - { url = "https://files.pythonhosted.org/packages/61/fb/da9e7a67a00a962d854196ab7abda0950d98ff3c84026471b7193bd9b3e7/loro-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4bf30afeac2e6e84b960319961738e20fa5971af939c3565f4fc03b0c82b766", size = 3549526, upload-time = "2025-08-31T06:34:08.852Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8f/0fe5d3c53ccb5b54bb145574f63271eb6dde05e5de62245102f48a500268/loro-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d9c9ff73707c249d2621bfc2733d9bfe174779d677d2bfbebc4286a08d6c687", size = 3284108, upload-time = "2025-08-31T06:34:40.832Z" }, - { url = "https://files.pythonhosted.org/packages/09/81/cdf2e42ab3b208f529d1e90392feda65aa32a6804601bf7e3b7d7b8b9148/loro-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90909ed9d6dbcf75b815f224f58823ca268720e24236510def584934f9ca95f4", size = 3233965, upload-time = "2025-08-31T06:35:37.529Z" }, - { url = "https://files.pythonhosted.org/packages/25/fc/ea1747005a7c48466ec6e45bc2016244e752729d03dd3a3b03ce824c9921/loro-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a62faca70f4182ffdb4f07351463a8961d311ccb272a928b62bb6e13ed85c9c9", size = 3495879, upload-time = "2025-08-31T06:35:10.917Z" }, - { url = "https://files.pythonhosted.org/packages/2d/36/62c69f184c71213c93284755a1254a42b18845aae21b0ee93115229260a9/loro-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f5890e041389da855c2cd0bb93dcf9727f53a9658bed1204bebe0c2d8154c1df", size = 3254467, upload-time = "2025-08-31T06:36:32.701Z" }, - { url = "https://files.pythonhosted.org/packages/f1/15/2d5ba4fc518b1299fad5e5c661eb026482604b9961f3f8a761c2971b31cf/loro-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:823d8d7766f13afd1c545522e107a30772bb907fd0e6fbad846c03c36b49f3ba", size = 3442732, upload-time = "2025-08-31T06:37:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/39/84/95b4017b75438dc657d862a69428bd53711eea44b07e027a96333eb55625/loro-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d85a7ef6f3b0a8a164baea6e9d49d0529d3783eebc3969df769d31005c74608", size = 3492390, upload-time = "2025-08-31T06:37:33.637Z" }, - { url = "https://files.pythonhosted.org/packages/17/91/15a65e3904cb0d6d19f8379392bd8027824df344cda930d7d2603fc3cc69/loro-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98c0c9e0c5f5bf8de108044a835fc652f1fa74dec84a99e8d93d6e9ff7c78b1c", size = 3398844, upload-time = "2025-08-31T06:38:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/c92682a7c82a5d32e2a4297ac9e5819983bf0fcd60d4203279d576ed0621/loro-1.6.0-cp311-cp311-win32.whl", hash = "sha256:d2815d1d5085a6472607dc220df8eb992a382910b792eef0e262f65fc381fa29", size = 2586198, upload-time = "2025-08-31T06:38:57.297Z" }, - { url = "https://files.pythonhosted.org/packages/87/c6/e0e6f278950fdb323ee5341d489fdc764decf7c984f42368b6aca8c87750/loro-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:40e6c4bda14968e8eeeae9476bb6fa6b5656771ce52469c27e4937db68c5ac8d", size = 2741746, upload-time = "2025-08-31T06:38:37.96Z" }, - { url = "https://files.pythonhosted.org/packages/c8/47/6b141983ad52e9a7ede355b8e9689355d8d3ad5eb6d4df059d06d8956088/loro-1.6.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2496b75cd88759c3186b9995ba516339b65564dcc130df934f25f4fab80dcf02", size = 3076956, upload-time = "2025-08-31T06:36:19.885Z" }, - { url = "https://files.pythonhosted.org/packages/34/8e/862d8d145b72409693bd27b9ff59da5ef55ec8e40063ea7ff5a86733d1f6/loro-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9889cda856d7002d10e973349ff81b1ad3542e944100b99bc5ac34d7ad7e746", size = 2860675, upload-time = "2025-08-31T06:36:04.722Z" }, - { url = "https://files.pythonhosted.org/packages/4f/22/1a6becfaf74c2e8b5fa1334ee659168d5c2bb89c82358eb93bf72c5722e5/loro-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42392369dd0bd55230dc62d5c76448d11cf413311de62765df10a64638248cf0", size = 3109806, upload-time = "2025-08-31T06:33:08.165Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2e/620f4c5f65319e640d0ed45b1f96bd1f60baf2373e9ddbdc175da71e767d/loro-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e7752c2f5971ffc66afc1107d2e72cdd12fdd1add3700df5b22daf460f5f153", size = 3186836, upload-time = "2025-08-31T06:33:41.792Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a7/62378bd909c8a74620e680a6e77d01f50ee62cd32efc9cdd73107f7b6851/loro-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d716ec3b94d20e928568c8bd6b508fdb2e3e3214e6eb6118628da5dd5c3b887", size = 3557219, upload-time = "2025-08-31T06:34:10.057Z" }, - { url = "https://files.pythonhosted.org/packages/ee/33/8df6ad7f2abb41d1aca9b597d6479691ab7d7ae15602d07713babd681bd3/loro-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2918f5138b13abf1ba2f0851250220414dec828f7d5f52a2543b8bcf1b7e6d4", size = 3290881, upload-time = "2025-08-31T06:34:42.258Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/e45204ec70b729faf642c2db69a00fa74b933597bb5cc40f92faf8b6b519/loro-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36af37e283444f3d86d0ab3616c58a4c27404c52cb2b7d630de8fc4506df409e", size = 3239072, upload-time = "2025-08-31T06:35:39.051Z" }, - { url = "https://files.pythonhosted.org/packages/ff/47/1272bdaef3c1f7d2b6e0c1fb85255c83449d844a75ddffe74372e2e44fae/loro-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44f4e1f15392187fb93c1e29c247253c498cf4228bc9d5126487459772c1b30", size = 3494669, upload-time = "2025-08-31T06:35:12.294Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ff/8abf17e1058a1d1a60d3782e842bf408646233757d31ed782e6e15729dbc/loro-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:59686d6b6757d3da0387e4bb070e5908d8c5f05655e9fab8deface0f9c4a2739", size = 3261553, upload-time = "2025-08-31T06:36:34.422Z" }, - { url = "https://files.pythonhosted.org/packages/31/73/24982d70babf02e8a1f54ec682c4b265c620fa8b7773f667859ec605d167/loro-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9052492752ddd7d84169698aad391ec66726f1855c1857f76ac56aa945110e40", size = 3448114, upload-time = "2025-08-31T06:37:04.067Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cc/061a23210d7e4e764a9465e587c25497bb611a40d4c4699b41505862a7a1/loro-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b41802007ef51afbeab1f489c95db8802761eabb2e1c3d264e5b211ae80ff019", size = 3491485, upload-time = "2025-08-31T06:37:35.411Z" }, - { url = "https://files.pythonhosted.org/packages/f2/15/8e2b1e876c811d7a32c9049cebb745a0b7a03a0242246a20ee06808d296d/loro-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83eddffc8d12695c3d5648ff6a749c131dce60937b272dd088177db05b3040b3", size = 3404615, upload-time = "2025-08-31T06:38:06.258Z" }, - { url = "https://files.pythonhosted.org/packages/d8/77/016e3606f7364a666f9e1d74d748bf41a9b32857ee7b5301e996c7a0521d/loro-1.6.0-cp312-cp312-win32.whl", hash = "sha256:d020b4efc36ca1a257588609b9a59cf35290d35a40cd2831ba86970d6e624679", size = 2585812, upload-time = "2025-08-31T06:38:58.755Z" }, - { url = "https://files.pythonhosted.org/packages/13/9d/9159bb19a45af35e81a4256f21c925b0f9e731c82bcedfbc7cf7d384b60e/loro-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe5dcb1dababd3fe705c2b1caa3621b31d52b26d4b8ef98f6fbc05b29e1cbab", size = 2746032, upload-time = "2025-08-31T06:38:39.339Z" }, - { url = "https://files.pythonhosted.org/packages/77/b2/cf3ac17e3104dcffebf11e1c0eeb9fd063f99232aa7ad1978b31174b5389/loro-1.6.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e81fabda7b05ff6b6a0d742fbd5aff950b07ecdd484bf43f3492ff9f24e5f189", size = 3076319, upload-time = "2025-08-31T06:36:21.428Z" }, - { url = "https://files.pythonhosted.org/packages/01/98/57fac35ec82302409586d564d389292733eb5c9f7e091fbf15f107439265/loro-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d417d6fccc7dea7c094aefdacd6096e93a1e9dc054aa40115ebab0e6cc8f2918", size = 2860968, upload-time = "2025-08-31T06:36:06.116Z" }, - { url = "https://files.pythonhosted.org/packages/7d/50/2f0348ad2105dd8945b4a0a63db22c90d5e770aaf7399c81c5c074056612/loro-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b1a4b4ecd316be844341be361bca04b444e1be12660fbb5ed0360eb02535e0", size = 3108951, upload-time = "2025-08-31T06:33:10.086Z" }, - { url = "https://files.pythonhosted.org/packages/66/5c/813492c3a568a84977482419d96c3cde2a44e0b6ade8823cd65cf47a727f/loro-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5609bb34d00fc767d396b6b23988aaa506549c719bfe5d88de3594ab96c328a", size = 3186291, upload-time = "2025-08-31T06:33:43.004Z" }, - { url = "https://files.pythonhosted.org/packages/b4/fd/8e22639c3fdbc5540ad42e1852e97cf8e6a705109ae4c6536795b02be927/loro-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1fe0618c18ecb11a36f6247e42b702c7810bac961b867f4a461a11de4fae70d7", size = 3555655, upload-time = "2025-08-31T06:34:12.372Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d9/ec32a72f863fffcc3935d1825271116dc4ea214944074b4e3738060815a7/loro-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa27a487bda836cf890c4be40350b9c449c72e9fe0be71e4b5f3344d476797c9", size = 3290644, upload-time = "2025-08-31T06:34:43.53Z" }, - { url = "https://files.pythonhosted.org/packages/98/6e/6507d029efe5caa9b4453812afcf4f294925a7723427d75c6c35075782e6/loro-1.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52ddc2121aca2c4d8922f8cd6301b53c485369056da38db96e301a0b46c35659", size = 3238641, upload-time = "2025-08-31T06:35:40.567Z" }, - { url = "https://files.pythonhosted.org/packages/ee/cb/b04036097a9a54db2753837e7e730f2f9a3331cd93a25dfca6f69a4e251f/loro-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1adaba4fae3773356067c0cc42a46ab1073c85dac021c1dbf7356a4c31b88a14", size = 3495139, upload-time = "2025-08-31T06:35:13.48Z" }, - { url = "https://files.pythonhosted.org/packages/e4/8d/0299361757a0dce5a67b49b9c7a7d0e2db4963b6da42ac861402fe01ce83/loro-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a33e42aca4680e3bc067d953dd2a4013f972ed7ff47c8e60cfe8a035d0b7066", size = 3260701, upload-time = "2025-08-31T06:36:35.673Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b8/e6fcc3e06806b21785dc4ed0c5df14ce671c7b9a52f1b1d9a4eac0de1bc5/loro-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f265a379207d10722d6be486fefc8f852aadb495423fbe2a4ccb78d5f7268885", size = 3448086, upload-time = "2025-08-31T06:37:05.512Z" }, - { url = "https://files.pythonhosted.org/packages/24/48/eb45ffbe89685af485aed662b9d5b566dda0f6b62010922ea5a8c1ce7176/loro-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4f4700233a7cca1f4ee6c515a617496a7378c9d1af235bd76636878622c3f265", size = 3491324, upload-time = "2025-08-31T06:37:37.067Z" }, - { url = "https://files.pythonhosted.org/packages/da/d5/55ebccdc7f974e651bc4c56f788d624a3a60bc299801ce3581d21fcbfbc8/loro-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d1386d9a32cdc1f84235cc579d0d038d24a8fe68ae8f19f9bc3dfcbe0b38e0b9", size = 3404214, upload-time = "2025-08-31T06:38:07.82Z" }, - { url = "https://files.pythonhosted.org/packages/de/f2/aef74bd9dcc65449ad2eb4adf2e392d52a1761e76de6b64b4094ec2da04f/loro-1.6.0-cp313-cp313-win32.whl", hash = "sha256:b5051d628d6404bec73a355cfd3e75b3d8c6be287b470956e83846bd000dcd3f", size = 2585936, upload-time = "2025-08-31T06:39:00.056Z" }, - { url = "https://files.pythonhosted.org/packages/56/c5/313975f7d41a2dba78d04d95051b4a0d8c98f278dc1916ecf8f4e78b972d/loro-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae8269e826c3a0c401d24efeeff1b95040df4c4bd8fc0b79f0a3c04005e31fc2", size = 2746678, upload-time = "2025-08-31T06:38:40.919Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6c/0146e45d16ad68eb846ffea9bffde704e6cb8b7c84948b410e251d0ac1b8/loro-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31d40258f35bf3316020370ee5c1b24bf46689142b1d27abb824c2b5a834f1e", size = 3108286, upload-time = "2025-08-31T06:33:11.662Z" }, - { url = "https://files.pythonhosted.org/packages/1c/01/c92dac8261ae03b49c4d5577382c2d52301346e670af556cd6492f413be9/loro-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2b335fc483c19cce84cfe2cf16a21d02a6df7c750906791c605c273f04695b2", size = 3181384, upload-time = "2025-08-31T06:33:44.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/09/212d5e86236bc4fe212c8edcb44c841dbd361ed0133c22f490fdfcc05317/loro-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:406b538128001c507fe3d922ff1efdc721be44b4d7d078488e76afee605a42d3", size = 3552402, upload-time = "2025-08-31T06:34:13.616Z" }, - { url = "https://files.pythonhosted.org/packages/46/63/3568800791fb7a44d9c7c052415e9554ba43cfb394536e14f56e85316f43/loro-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f2fc4db7b5dfefecc5caf7cc4cee50538369fba7adde235ecdde85303426a9a", size = 3290210, upload-time = "2025-08-31T06:34:44.94Z" }, - { url = "https://files.pythonhosted.org/packages/91/9d/48482b135fc7366931a73cdb4af43cd050b21595e6059e1ca660613e1690/loro-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:625697361cf71fc95e4a855cc2ab523ed8e573d9d338485536856559b2446ad1", size = 3258404, upload-time = "2025-08-31T06:36:37.044Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a8/7071db3470ec51a1fc5fa875517c4bd128a742045f55cceeea0bb892309e/loro-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:423bda709359390cef568c5b6495b8fbba3097306384492d440dfe3b917a7a8a", size = 3443698, upload-time = "2025-08-31T06:37:06.895Z" }, - { url = "https://files.pythonhosted.org/packages/48/22/713b4bae9847b22c59480362ebb00cca981e46874691b4676cb8c1b575f6/loro-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:975bd2d570a216e15d74edf1ab62015d6d501b1f2e1c37d27c69f4b79a776e7f", size = 3485093, upload-time = "2025-08-31T06:37:38.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c9/af48a3628e67fe11964717e532892fc007c569567af9db243bf27e180e1f/loro-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bbb46223a00a128bb8d2aeadd885feceb09f5d3f9ce9a76605fb6e1f0633518", size = 3402754, upload-time = "2025-08-31T06:38:09.316Z" }, - { url = "https://files.pythonhosted.org/packages/82/2f/89c50eff27346d646c765383ad8fc01d27dfe4fba8d0caba3f1079dc9e3f/loro-1.6.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:12c3534766c63366af320704821e0edb04c302081ca8eef315cfc08058680204", size = 3060404, upload-time = "2025-08-31T06:36:26.03Z" }, - { url = "https://files.pythonhosted.org/packages/08/b8/eb993ebad42271a23add5c99bbc1641e2388864e8637b3bdb8e8ecd52466/loro-1.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20678ee6f37f5e78f5e8262a83510cff3669e5fdb2a34c23c7e82c2389604295", size = 2850228, upload-time = "2025-08-31T06:36:10.409Z" }, - { url = "https://files.pythonhosted.org/packages/c0/85/8a7ab8888101f56f93ebaa81c5203039f9544e8f2c0df3d4abd3b0a17423/loro-1.6.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2be102c1ab43fa0c5d592c2b26d81ab7b0f48efae69dc1940afd35d3f5b65d5", size = 3220322, upload-time = "2025-08-31T06:35:42.034Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2f/8c44a25b506a589df6359cde382e08c6ea35ec57165a19197abf8429e6e6/loro-1.6.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7f2e6aa42107ad89082f2cb7d873ee4dc5d91cab801f63a6aa9ce2e87c82d", size = 3482803, upload-time = "2025-08-31T06:35:14.978Z" }, - { url = "https://files.pythonhosted.org/packages/b0/01/b0f290185a74166a948d2402da810c45b9071cb454680c6e688fab06d90f/loro-1.6.0-cp314-cp314-win32.whl", hash = "sha256:1422b01d780218bd87a6dbe738c885b27d7034bb8d34b269876cadc379e484ab", size = 2572334, upload-time = "2025-08-31T06:39:08.335Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f641421fa11c7fde5dd3ed78a823ebdb19dcc7a5b09f2e07927db59bea67/loro-1.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:532c2fd24d5da7aafc1375389113f61da02aa7061e286cc96fb265d9c73658b5", size = 2725881, upload-time = "2025-08-31T06:38:49.383Z" }, - { url = "https://files.pythonhosted.org/packages/78/34/8eed8d59fad088cc5d4c610b661c4c366096f32f5697d1bee8b7bebc6249/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f10366120c83a1527496d0cd86a6d5a24f26bbca0dfb3c2171622473de2258", size = 3102734, upload-time = "2025-08-31T06:33:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b7/9d24917ee48e4e89a655c10b5ba23c80aa6ec23952dc99a589fd549748d7/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3349e34bca027331aaeba2607d51135c1c5b9246562a0a881a7e1c89c7227237", size = 3182426, upload-time = "2025-08-31T06:33:48.779Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b9/464e254d0379093195ce4468a4d39a6a1bec2cd24c489cde0ec8abe5d46d/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb81ea3673e861f39cff0cc000a06e1339c8634fd216f19af3ed30835d628f16", size = 3547376, upload-time = "2025-08-31T06:34:18.028Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/7298f8326be9c9631f77cd6df40e32c71521ceaa4e08394abac7601c6f40/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0548dea0d701ec056f6c68f7496d876fb86201446fb3f940fdd566d69f9627ef", size = 3285967, upload-time = "2025-08-31T06:34:48.635Z" }, - { url = "https://files.pythonhosted.org/packages/73/17/8026f05268a0a2e1b744042fc338836aae04b6ecb6fcb89bb10abb9f9651/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1389448c78fa64a55705af704482bc3bb0754df51715a9471fa83b4b3f74b0cb", size = 3230191, upload-time = "2025-08-31T06:35:45.951Z" }, - { url = "https://files.pythonhosted.org/packages/49/e5/19e0f11dc4e537443d24534be5bb2aed64717bc508d14d31c16a702d6664/loro-1.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cee6a723915f15959958ad26a4b5eb26d6bc34fe03f28992af9a77cd7941e453", size = 3494089, upload-time = "2025-08-31T06:35:19.119Z" }, - { url = "https://files.pythonhosted.org/packages/bf/80/5116b5a3151d09dd1f4a77a97083778a7bcd998077128c99b535b748e161/loro-1.6.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:41c64a5ed005430f5d56e396e92409927633a0ff43b8730cc78847b2f63bc18d", size = 3248686, upload-time = "2025-08-31T06:36:40.947Z" }, - { url = "https://files.pythonhosted.org/packages/20/32/0d75a3db6130ff829137f602841404c05e43c438bac8c7efbd84fd940e8e/loro-1.6.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:8171826f1693bd16409c87a65f95c1b6691cd998b31bd084677d050f0c59ca01", size = 3445249, upload-time = "2025-08-31T06:37:10.798Z" }, - { url = "https://files.pythonhosted.org/packages/45/11/2d7924c0799339f0c154c92a7a22339581f55443c65a64a02ef1feb1e624/loro-1.6.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:c4070e71cd24481def9a5c88b2746b3b5ceb2f73e22d92f7ce59e0bb7fcc332d", size = 3492260, upload-time = "2025-08-31T06:37:43.097Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4b/9389892c11cb5d4ceb8a009ba5763545de6e6870a791da14b526faa7bf80/loro-1.6.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:531385e649a955dadfab220d9dbd78775c8d32fd769e4d7035e05b09b9260699", size = 3394747, upload-time = "2025-08-31T06:38:13.595Z" }, - { url = "https://files.pythonhosted.org/packages/5f/79/c9e99586a85527137b01a313410deea1bbb4bb9f415aeb876a771b99495b/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:072107100e82c61b0c05f57b6f800d9e8f1a02ec0b4ecacd5a3778149daa9518", size = 3102100, upload-time = "2025-08-31T06:33:18.09Z" }, - { url = "https://files.pythonhosted.org/packages/77/7d/1c2a5d4996050d96adc250f26487173e9611f9c2ee8d2f6c76b75d8bcc28/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5451b3f839e639a97be198ac2445e4f3dea6c9b1ad855cd30e1b4d7c0e23e2f6", size = 3180654, upload-time = "2025-08-31T06:33:50.155Z" }, - { url = "https://files.pythonhosted.org/packages/68/a1/f7fd3b6a221123b8be35e7b4cfb3dd59df78029d4e62f39659394023cd3f/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:670478fbd4f4627b7dc621e5fad60dc0c6dfcdbf301f597d41a2230674e1efb8", size = 3545840, upload-time = "2025-08-31T06:34:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/42/af/29b75a8dea8805610f593fa09ac40a66b5cfd04e6394a9f94f3488da3427/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4362026663e843d7880dd349a342d648771df2db2e72185bc70681afb92ac4c2", size = 3285033, upload-time = "2025-08-31T06:34:50.028Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9e/77cc17dfae4dda569b3440b1a38897fe581db678b25575cebb6a45936761/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a4eb959e68e4b5bc66724e558e2c12572e230bccadac10b327dbc2334e5df1", size = 3228359, upload-time = "2025-08-31T06:35:47.175Z" }, - { url = "https://files.pythonhosted.org/packages/0d/51/c453c7fdc6c7cdfd7a77f33530d00c26d751ca88c68927c820662159b58c/loro-1.6.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:038fb9e98545f66f4def559612b9010a4d3aa73ac91a3ca4c500a6389fe08d74", size = 3492414, upload-time = "2025-08-31T06:35:20.742Z" }, - { url = "https://files.pythonhosted.org/packages/08/69/af20a5a831d6fa5eca78a6b58502d3091d97471989c23988c7ed17c9df63/loro-1.6.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:32e2bd06e1c617702203514cb98b6f2054a3e3d576f5650bef4d7b5992a4486c", size = 3247127, upload-time = "2025-08-31T06:36:42.35Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/691dc1c0843313aaddefebac1fa2290509990610fda4e29f2300a4c40054/loro-1.6.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:7683ad91f36bc8c965143279d6f850a93347ff05f6762c77f3aca6fb3d4716d0", size = 3443222, upload-time = "2025-08-31T06:37:12.08Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0a/00ae2b7abcd4fef99447d2a701985fa0a2436ccdffc53749dc48cb7683fb/loro-1.6.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4e498ec9222e9b7c1c14c21e14a0fb98a3e74c29909c9cc6a4071686a10d7cae", size = 3490895, upload-time = "2025-08-31T06:37:44.945Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d9/d8fe4d18401246e4076a26abb0a687fe186204ad940f85befa2e13a4aad7/loro-1.6.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:a92053a5a904ae1a738fab2cda17e55728bb5fa3c78a090c5b6c3b35821af507", size = 3393333, upload-time = "2025-08-31T06:38:15.257Z" }, +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/be/4e00ced4b8f2d852dc581109de9b4cd9362395e276b509eece098c42eedd/loro-1.8.1.tar.gz", hash = "sha256:22cfb19625bd7245e9747ee9d43b10511c16a35775a38cf914dc74863c4dbe88", size = 64093, upload-time = "2025-09-23T15:53:20.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/14/559484f1131da6df7622dbc66f4ee81e6affe913abc3e3876a3caecbb25a/loro-1.8.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f2a0c67b59eec3f945125f09a680587a3410039259deb84c8f2a9d2f4af7dda6", size = 3108081, upload-time = "2025-09-23T15:50:32.45Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4c/d317ccb8ee9f84eabf8624902a7c445022ab1dcfe02db00f01e03254c8c7/loro-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a63d3ffd863e9684188fc7476798a43ca66347ca674b6bcde0bc462f6852c961", size = 2898476, upload-time = "2025-09-23T15:50:15.738Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/3f58cbe1641e15b8307184a8875f60dee33caa19ad25822ed2bbfad0e650/loro-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:474ef033dafff11e1b5c17c146c085cd7500ff904ebe8b064a7e3f1fb498d699", size = 3109969, upload-time = "2025-09-23T15:46:45.928Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ed/ba95aed11a8efea18d6cf790f6baf718794efbe0a895ed032e59cfc54b2e/loro-1.8.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb210da4a08dd654464ef72ace7ea2baf51144866964a8d54d0a0f83413f09d5", size = 3181724, upload-time = "2025-09-23T15:47:25.28Z" }, + { url = "https://files.pythonhosted.org/packages/1e/80/ae17d3444eca463bfd360389f965d1474febd048f6a9d6af70d9df7eb93b/loro-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f29517fc9014627f6e7eac2b35db989c46ff4abb46c8a533a19a541355745c0e", size = 3564914, upload-time = "2025-09-23T15:48:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/86f198ea0ccbc305ff1c75b8d5ff0be905e00eb2211b54c54719db387696/loro-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dbc30440460e64fd7c2e92dab41489d82744a2d18a17e3fad0b5686f3a14f40", size = 3281250, upload-time = "2025-09-23T15:48:39.086Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cf/cfaf3f7ced262111244e39d852fa81fc41ad9a423a34992c091e5c45e7c9/loro-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f01c94871ab0993fb079010bc643ca92726f86f865cbe57f941c0a16214b5077", size = 3168915, upload-time = "2025-09-23T15:49:45.562Z" }, + { url = "https://files.pythonhosted.org/packages/17/a8/b5f8289d4a461a75a25ed0cc4a68de84729523def38307f4cf01d8c6983b/loro-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85ba11fa39accf134f0f684cb79c323da0a284fd88be96b0ff37215c765e6f0", size = 3507146, upload-time = "2025-09-23T15:49:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/72/03/d5921fe5f8c5257f1af926f1df2efa3caa0954e6928722f9b3705204ad5b/loro-1.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa6fda1f5bb661c390326214a1c853b561ae0edb9e20ec51c12c3fea71e184bc", size = 3290396, upload-time = "2025-09-23T15:50:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b5/985a0bfda2aa14d8498aea33acf535ad14653e76d409c1d9f6423b4807e1/loro-1.8.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7f3064c3a38bee0d62b22a9b4b3aab3a56f0f3db74419674d896f76ca606a95b", size = 3444801, upload-time = "2025-09-23T15:51:27.363Z" }, + { url = "https://files.pythonhosted.org/packages/09/ae/ffd784032c2ebd463cc478c489b0f7f1573638ea19e08406d4d120c85178/loro-1.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a76344c14f7d4d7fda774b05c4a9bf87b04945bc979737d632936627d6c7a756", size = 3489000, upload-time = "2025-09-23T15:52:05.337Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/3b56c29f977cc4ded12c7a8ad3103034e3284a783a17cb8685cc9dade180/loro-1.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7e4af0230f2392b54687cd94b6161c3b096497b8cc4fcabbe2d246fa59cd5dcc", size = 3386907, upload-time = "2025-09-23T15:52:41.853Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3d/9795cf540be48941bb91eff210464304cc0a31763df9be49675ee1918de1/loro-1.8.1-cp310-cp310-win32.whl", hash = "sha256:1530168f1ea1425fce1b3fc438a2ab9b47d854d65b89c30cc3f2b3d5791efd5c", size = 2598112, upload-time = "2025-09-23T15:53:43.356Z" }, + { url = "https://files.pythonhosted.org/packages/96/ba/f3ca9f292f118f93348c673e50412082f1674a64ccfc7ef63e46f425f1e2/loro-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:882d72361e5c92780930c15e91a691b894ff0a129c6c098eb1f2129bdef59da6", size = 2740192, upload-time = "2025-09-23T15:53:21.396Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/341b48d2b6ac7af877b009361438f76de550b3161f9946e68f2ebc77bc47/loro-1.8.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8bd225a2697b76b5d3d7df0a9bd4e37aa68ff9d46cd9ad3f00b57bade3fb1642", size = 3107035, upload-time = "2025-09-23T15:50:33.74Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/a56a0761df4fbeb551d0c9146533cb1a21bb8236e475f791e62263b8c1f2/loro-1.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:12a3d692d1f3c29e89c740f4aef7ab251bc2d6593279aa0e4e83eaa70c59e9a1", size = 2898298, upload-time = "2025-09-23T15:50:17.445Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/162807e021e2ae3d4fc8b7bb87a2f7f559ff84014611f351528549c01b13/loro-1.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec12410983b9a9a431791bae5f16585bcbe10ac5bda1808acec91b7aac27bee9", size = 3109377, upload-time = "2025-09-23T15:46:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/7f/dc/4db74a6e36399c18687e4f9cb4b5c5e945839362b0d1b22d2492e89739ff/loro-1.8.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee0c47132e2e9e75c7dcbe28ea003ab2d3ec9c3cf1a18bfc2c706c9bd3805e79", size = 3181693, upload-time = "2025-09-23T15:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e4/5af09cf5c63b3a7c01f56194a0589eaaee64d58fd77bcc123adf20a79584/loro-1.8.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1031d717af5515d63335843466f6c1726d0ed609744c143b83f41097caa16762", size = 3565385, upload-time = "2025-09-23T15:48:04.998Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/63855b31146277e5a29cfaa5924df1559ca0ff408b86fddc96e46f69e1ab/loro-1.8.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:578bd80bd64131aaf3a0e7b2dbb1aa7804c99eedd7ab0a43fd8ea4f6a6d43c35", size = 3280889, upload-time = "2025-09-23T15:48:40.404Z" }, + { url = "https://files.pythonhosted.org/packages/e4/20/b73b3e010371126a5778c93f98f36da880f7f7573ec04903d900414f0768/loro-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88db40a908697bbe37981fcb7e732117c1780ac14b11efd5805c5bd8e04cd3ed", size = 3170921, upload-time = "2025-09-23T15:49:47.134Z" }, + { url = "https://files.pythonhosted.org/packages/40/97/3bcfaea321dd9ecc34e68cb0f8994c58e8fd805eb881975b7a73c321fbfe/loro-1.8.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d87de3ea9999e65801dfbb0b04a64aad39402c76a934c398a10efd7b5d3394e1", size = 3506727, upload-time = "2025-09-23T15:49:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/07315509a3956b76ec4c7cbc6113c82d1096544e4de6b552889612ed8529/loro-1.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b18d4917f0df0df81d09aff5fab034519a3de818e7160d8a0d86b080ae7fdc1c", size = 3290762, upload-time = "2025-09-23T15:50:51.314Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a9/2e1e8f77696e4106f5313248707ae5aeabfe20fa22dc6528e5ca2f280317/loro-1.8.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8e44e48704b0391ced218178064ce479ee24e56386605fe75472b16747af3cb9", size = 3445333, upload-time = "2025-09-23T15:51:28.96Z" }, + { url = "https://files.pythonhosted.org/packages/78/57/11ee3e0d972a959a93fc7fbcfb0076547b9a53bf00c9b0ccf3787b46f20c/loro-1.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:95ebc4c28558875cb8eb5d1b1d3faaf5c7711895af45baafdfcbcca4c7965087", size = 3488752, upload-time = "2025-09-23T15:52:06.808Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/8c9f96fbcf17ae7883e126099b38ba34a038bc08986b7cd65564de2e977f/loro-1.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:563836006b4e254614b94fac76c94c9b5823b8fbe97755027cd94f1c0bd86103", size = 3387391, upload-time = "2025-09-23T15:52:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/72/a1/f0482c6f01ab452af89cc6f5950629f62b4cfa5f254a0f8d8018db98cb07/loro-1.8.1-cp311-cp311-win32.whl", hash = "sha256:6e59d893deedf4ad84a958f42bf00dcdb235293e86cf74c4d58bdc517e96a17e", size = 2597184, upload-time = "2025-09-23T15:53:44.74Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5f/d1e001ca135a6e54b8acf07dedf461e6326b7da93fa494a197a0b16d3955/loro-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04789df148e7d077ce2e206cf60e3d908fb8ced56460a7a7abcf12afcbf3e39", size = 2737770, upload-time = "2025-09-23T15:53:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/00/e1/2d381182a111ca8cf4f4869bcf43e68c4ebabf1d84da4a08eda355834547/loro-1.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9eeab61cac92d504eecd580c577becc12a0a3830141b17a49812ecaf5d3f3ebf", size = 3088136, upload-time = "2025-09-23T15:50:35.106Z" }, + { url = "https://files.pythonhosted.org/packages/3f/9c/00a5476efb54b1091145ed3c7dc0d5961f283b407e7608b649d00ded4a28/loro-1.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1f08fa1b4bdc79901c763d81361537ca5c086560acb80291f5d6fe163613c603", size = 2878906, upload-time = "2025-09-23T15:50:19.017Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5e/e55ba22e04313979c4f0eb74db1100c179c592d99cb0e514e60a155bbf02/loro-1.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420fb0112115dc85b8abd392e18aa163c7fda72b5329be46e7d0cb2261ef8adc", size = 3114935, upload-time = "2025-09-23T15:46:49.989Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/55deb84ed33d1d8a4f45c112bcb36d00701d8c94bf3f2071e610a993b36e/loro-1.8.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:97153d8929cda5903fdd5d681a5d0d4a28382e2707b292cfad6a387f4b73396c", size = 3181672, upload-time = "2025-09-23T15:47:27.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/05/181f8051b2142c28e5cf294ac5f13b34bb3e3e802d256842010e05c29596/loro-1.8.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:934362a533d0ebf75216799f1305252502a2e9733b3a7bb311012c4b8495f541", size = 3567186, upload-time = "2025-09-23T15:48:06.357Z" }, + { url = "https://files.pythonhosted.org/packages/03/9b/e91146ad0a0cfb73bd47f39e69685ab3e8654aa17875f1806ba484be88ef/loro-1.8.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7c777cd9ddc13cde9d0ec887ae3e02879d2f861d5862a0b6efd29fe4eff30dc", size = 3286193, upload-time = "2025-09-23T15:48:41.849Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/c07116cf6a22dbcb5d7d7d693b184358f8a59737290076c98108f17ffb29/loro-1.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca9d40e6e65d748d36867fa623275d3b3bdb7c1da68f4005bc17f69a57034c0", size = 3177660, upload-time = "2025-09-23T15:49:48.468Z" }, + { url = "https://files.pythonhosted.org/packages/83/05/8ec0261ac604b76a716c0f57afbf5454448b1d82f0a06c99972ae89e28de/loro-1.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca5bd845dc9b880a3fcbe1c977549157ed3e22566a38ee3d4bd94bfd76e12e50", size = 3507836, upload-time = "2025-09-23T15:49:19.107Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/1be760344ca3f9cff3732b6d4ea0c03a9118b479074568bd9908dc935b30/loro-1.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:48884003f2268d83864205445ac8302894c0f51c63c7d8375c4ffd8e100e7ced", size = 3295335, upload-time = "2025-09-23T15:50:52.636Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f6/b6362dc3103e45e4f3680d6c8df44c7f5a3e266c3940119956b0120e1b7a/loro-1.8.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7392c983eb8c6fa062925dcca583dd2d635ea16105153b0cea3a0f40333bf60c", size = 3444357, upload-time = "2025-09-23T15:51:30.694Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9c7537846bb6d2a1267adcabd202f02a3c3fa7a3fbcf6537106574fc8fd9/loro-1.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03f9e0ea6c72929cacebef44e698db50e0e9caa77cc4d87d43a5b5836896a5a3", size = 3489985, upload-time = "2025-09-23T15:52:08.234Z" }, + { url = "https://files.pythonhosted.org/packages/7a/8a/66b7859080d9017ecae74d7835fe2419dfd27435382195d508644530b141/loro-1.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e0a0188d120bce70a64c58731330ed9b495ae2034061b5a3616b2b7940ead89", size = 3393867, upload-time = "2025-09-23T15:52:44.75Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9e/7c83206c10f8cb38532da00f0814cac0e6207956a6a39e5e183227cece21/loro-1.8.1-cp312-cp312-win32.whl", hash = "sha256:90a02ac5c85629d920c4767dc4b31382d21bde7af93d5dc4d3a4fcde4b4fece0", size = 2597517, upload-time = "2025-09-23T15:53:46.401Z" }, + { url = "https://files.pythonhosted.org/packages/af/86/4357a818e5a03d1be1fa62cc1c0591b19b8a5e71dd00d45a7f8e8b48b28a/loro-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:92a31a8613fc6d9bb33a64767202e19592ac670618a174c0fbc940e31dba9d87", size = 2741953, upload-time = "2025-09-23T15:53:24.587Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7c/e0f6d6376dedb504e826b09a71bb871f4c032c2c95db0f96ee9f1b463a17/loro-1.8.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:55ee9ded98d6e328f210a1b9e2f01e8befb6994da82dd03756c56d8aa047a2ce", size = 3088156, upload-time = "2025-09-23T15:50:37.613Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3c/9fa9fd4a244539943df17c4fb3e3c5e90f0726731b9bf59bfbd9e57b09bb/loro-1.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4dcc9a3f558912d0ba2d39954f8391084e987e7970b375bfd96f67d9499ad4a0", size = 2879185, upload-time = "2025-09-23T15:50:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f2/48ab3634a1dc3f5951e05905d93c7e9dc2061d93e1facf6896f0d385cb61/loro-1.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a2b318bb08d1bdc7a0b8467a7ec6d90c7c46b0c58e7aafc9fc307825fa868f7", size = 3115017, upload-time = "2025-09-23T15:46:51.372Z" }, + { url = "https://files.pythonhosted.org/packages/00/a1/7a80b48fca9366cb6867e4394b80dae7db9044e3f1e8ed586d5dfc467c2c/loro-1.8.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a7e09cb68da97559cd103a93ae69004bb929fba3db6a13846c83ac979698ce", size = 3181487, upload-time = "2025-09-23T15:47:29.219Z" }, + { url = "https://files.pythonhosted.org/packages/50/f9/881d9a4658f5d33ac822735ee503d8e5590db552a1ac3f992a36a4fae03d/loro-1.8.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc5acb22ca0ae3e6793024cfc3ea99f200b57c549fa71e48cdaedf22cde6fe19", size = 3566686, upload-time = "2025-09-23T15:48:07.701Z" }, + { url = "https://files.pythonhosted.org/packages/be/6b/3ff95d187483b0f71e026e86a3b3043e27048d9a554777254b8005f396c8/loro-1.8.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92b19a44b16e86dced2b76d760715f1099aa99433c908e0fe5627d7897b98d", size = 3286348, upload-time = "2025-09-23T15:48:43.416Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/414915e26d2463107425f3ff249a2f992f2b15d0f98d75c99422fc34eb48/loro-1.8.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eed7933a3e1500c5a8e826c5faf7904ce253725512234eb2b2bfb01ca085217", size = 3177439, upload-time = "2025-09-23T15:49:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/538488ceb0a7b857eadecc4e46c6bea20df2b9f6ad1660ad6d10b201d931/loro-1.8.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e62783de33f8bf0cf8b834defacd4dd62d1adb227d93d9d24cc28febf9f53eec", size = 3508131, upload-time = "2025-09-23T15:49:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f0/8c06a5ae198c7fdc636fd40cf6edc604b45e51affbd537d099eb93a95143/loro-1.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8f394e65acd54af19b5cea796d9d9aa4e512f7979f8514f6938fd9813a753f5", size = 3295009, upload-time = "2025-09-23T15:50:54.272Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4a/2fb82afaab5899cc3a05d31e4059aded41571e6fd5c310cb5bc5520c563f/loro-1.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6205cc3fcb75b4913678ca399ab97abab0f253c8f72ece637d183979c06d19a1", size = 3444600, upload-time = "2025-09-23T15:51:32.046Z" }, + { url = "https://files.pythonhosted.org/packages/f6/87/4b9ac56d371c7a4b85ea223ca17b7ab33de858dab8a1a176ad33af9d7cb7/loro-1.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1cd5d028309f8ae94b14b7b1fb3a6e488b8a09d205a37d44eb3af04061be742", size = 3489090, upload-time = "2025-09-23T15:52:09.704Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/abf2a9f9f6c0cfd6ccb940fa81d9561767d01d43684505884e404ee4e930/loro-1.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3da7bc3cc0e8b04f094bc52c3f416f86be4c3a332dcbd9428b466d98329f26f5", size = 3393536, upload-time = "2025-09-23T15:52:46.284Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/76136846dc793e96a34f73220d65279f7b7f391a3446838fd095bf804d73/loro-1.8.1-cp313-cp313-win32.whl", hash = "sha256:a90e5d56a030e284a998b73a1c55c5b8c5f62f96bee4cc017b88ff815f9fb743", size = 2598079, upload-time = "2025-09-23T15:53:47.994Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6e/dfd0d18a7bd7d90b111cde4e628e0fc26d70307caae33f3ee6d28094125b/loro-1.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:fbee625170327de616709af943410b72c5a4c12ebd8f7dff6324d59aa51da5b2", size = 2742638, upload-time = "2025-09-23T15:53:26.074Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ac/e134286c4275af5ab0149ee1a200c64f35df2cccb1b70142af04b509ed7f/loro-1.8.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6f3dfa58cebfe1f0e08a8a929303338c506733dd8650afd3d1f3ac70546ece", size = 3109397, upload-time = "2025-09-23T15:46:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ee/578a588f5f0a642491b852d0bc7bbec90e6a93fa95b12c4e22e7514d156e/loro-1.8.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c632a0a4a00f4a73df32fcaf266320995f89b68fc5f1d875efc979cda810babd", size = 3182019, upload-time = "2025-09-23T15:47:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8932e77fb6563fcab5ce470a3b754a758b8ce743a389b14ba9c436cd5d/loro-1.8.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:888de919b082ace4cb88f7244aa7a5263233604fc0fb9e7571703940a6897be2", size = 3562136, upload-time = "2025-09-23T15:48:09.165Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a6/440e9ff25150908e9e91362fed32097c008956ff173e9d852adfd06ce25f/loro-1.8.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1689f8fd79dc76f75e4bd027e9685bb73b44e0b60cfc0412d78369da300e6f68", size = 3283645, upload-time = "2025-09-23T15:48:44.959Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/4444939a3d244242dbcc14c98789c7c89d2468cb541629695335a953cbc3/loro-1.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:028194142bc4b628ec0f926018fbfd18d92912d69eb2f57a14adf4a3ef1fc7e7", size = 3288947, upload-time = "2025-09-23T15:50:55.972Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/70201ccf7b57f172ee1bb4d14fc7194359802aa17c1ac1608d503c19ee47/loro-1.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:9e6dde01971d72ba678161aaa24bc5261929def86a6feb8149d3e2dab0964aea", size = 3444718, upload-time = "2025-09-23T15:51:33.872Z" }, + { url = "https://files.pythonhosted.org/packages/14/b8/01c1d4339ab67d8aff6a5038db6251f6d44967a663f2692be6aabe276035/loro-1.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8d3789752b26b40f26a44a80d784a4f9e40f2bd0e40a4eeb01e1e386920feaaa", size = 3490418, upload-time = "2025-09-23T15:52:11.183Z" }, + { url = "https://files.pythonhosted.org/packages/60/67/88e0edaf4158184d87eee4efdce283306831632ef7ef010153abf6d36b82/loro-1.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ab04743218b6cbbfdf4ca74d158aed20ed0c9d7019620d35548e89f1d519923b", size = 3389761, upload-time = "2025-09-23T15:52:47.785Z" }, + { url = "https://files.pythonhosted.org/packages/54/fb/ccf317276518df910340ddf7729a0ed1602d215db1f6ca8ccda0fc6071df/loro-1.8.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:29c25f33c659a8027974cd88c94f08b4708376b200290a858c8abd891d64ba15", size = 3072231, upload-time = "2025-09-23T15:50:43.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/87f37c4bbef478373b15ad4052ab9ee69ae87646a9c853dda97147f4e87a/loro-1.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a9643d19eee7379b6980fc3b31a492bd22aa1e9aaa6fd67c8b5b4b57a0c7a1c", size = 2870631, upload-time = "2025-09-23T15:50:26.223Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7f/b0d121297000d1278c4be96ebaed245b7e1edf74851b9ed5aa552daf85eb/loro-1.8.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323370115c37a793e805952e21703d8e8c91cc7ef16dd3a378043fe40174599f", size = 3156119, upload-time = "2025-09-23T15:49:51.227Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/35c62e7acfc572397ffb09db60f20b32be422a7983ae3d891527983a6a7e/loro-1.8.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:69265d6751e536fd7ba1f04c6be200239b4d8090bcd1325a95ded08621c4c910", size = 3492080, upload-time = "2025-09-23T15:49:22.137Z" }, + { url = "https://files.pythonhosted.org/packages/23/36/543916bb43228e4d13e155d9f31cbe16cf4f995d306aa5dbf4aba2b44170/loro-1.8.1-cp314-cp314-win32.whl", hash = "sha256:00c3662f50b81276a0f45d90504402e36512fda9f98e3e9353cc2b2394aa56a5", size = 2584938, upload-time = "2025-09-23T15:53:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/8369c393107cafcaf6d5bdfe8cc4fead384b8ab8c7ddaf5d16235e5482e2/loro-1.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:c6ebacceed553dad118dd61f946f5f8fb23ace5ca93e8ee8ebd4f6ca4cffa854", size = 2722278, upload-time = "2025-09-23T15:53:36.035Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c2/3a0b060a20b0b3fa96fc2bd43df8ae33c0625c771c4d5da49689718b4604/loro-1.8.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:135fd29263d5ea04f6b86c6b49aa5ea3f9955961697015776b48a1fd60f4167a", size = 3108015, upload-time = "2025-09-23T15:46:58.075Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6a/3a22838ccdfc30d0608d5ac1e706e1770abe4cb00dcc422bbcc2f335343e/loro-1.8.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cd94190707d37fefaacc20cae2b3aa577e5a2ec9ce454ca793141279f5da64c", size = 3180036, upload-time = "2025-09-23T15:47:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/cdbd22833831e8a3f0a1781e7d6c5daf6a67c8ee09a63b79630d4b2115ed/loro-1.8.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f68c715168a8c9f5d184c5c8cbf20156b17c9d8e474475c9085e21327b7cc27a", size = 3564717, upload-time = "2025-09-23T15:48:13.587Z" }, + { url = "https://files.pythonhosted.org/packages/6c/34/4c95bd5846bd42632d9266016d4c9dbf3cba1430e7a1e6a756f16bf48f34/loro-1.8.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbb321a71e785dce359aee218738369e93fee9feb29c152c4f2f87f9264846fa", size = 3279659, upload-time = "2025-09-23T15:48:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/60/2a/b9582b09bc43391887e7b2f9218f7944c6c2b1867e88c36999dd09888321/loro-1.8.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:497303d8cef6613527238a4b52fdccf032f57c74ee48a98986d3262674fddd22", size = 3288737, upload-time = "2025-09-23T15:51:00.536Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/782299a8bb69d6fe86b39235c22562de16819c2fdda4c560e3fe20fa3386/loro-1.8.1-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1e6af52a70bbea506222c92a4b033827e282b7c442aa5351fa7a594ac1e90319", size = 3442067, upload-time = "2025-09-23T15:51:38.079Z" }, + { url = "https://files.pythonhosted.org/packages/6f/65/55c8eda1c850eb061ce9313182937fde84211398539e2131bd65146675fe/loro-1.8.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:83ada2ae25316b52750ef5b78803dbfbd727b99dc2d9d2b7477d04294108675b", size = 3487659, upload-time = "2025-09-23T15:52:15.833Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/3a09a523fc5d9a43b00fa57b2e4e8ad01acb2b3a57f8493f6f63d34464fa/loro-1.8.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:478f3b0eefbb67121bf0622c996bd0d33aca1e31fb6bc87f776934b66797eab8", size = 3386622, upload-time = "2025-09-23T15:52:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3b/2d13e114e6e4e0fed0e2626d00437b9295b4cf700831b363b3a5cebf1704/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3767a49698ca87c981cf081e83cd693bb6db1891afa735e26eb07e4a8e251eb", size = 3106733, upload-time = "2025-09-23T15:46:59.98Z" }, + { url = "https://files.pythonhosted.org/packages/d3/78/c44830c89c786dfa2164e573b4954ce1efca708bcffffc1ea283f26dbfeb/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af22361dd0f9d1fde7fe51da97a6348d811f1c89e4646d1ae539a8ebf08d2174", size = 3178590, upload-time = "2025-09-23T15:47:38.454Z" }, + { url = "https://files.pythonhosted.org/packages/b4/1b/3aea45999e3a3f9d8162824cee70ec358b5a7b0e603d475b7856c7269246/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b499c748cb6840223c39e07844975e62d7405de4341ea6f84cf61fc7d9f983c7", size = 3562843, upload-time = "2025-09-23T15:48:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/d5795cde4cbddaa1954d8ebba3a133aae4900d27866bc2bd7d5ce053837a/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a83d4aed44ce2845012793110e4381fbca48cd9ad7c7534567e11d8d6b3fe6a", size = 3278780, upload-time = "2025-09-23T15:48:51.042Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/f2f9eb748ba64835cc58488b0e41ba1fc65b4386332d04563cf61f066035/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d2375b3a37574345e1744e6580b278e50265f496573a7e2b2523d452a67766c", size = 3167056, upload-time = "2025-09-23T15:49:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0e/3c43fa589feb18932222cf76e236dc8314c206644a43aaf8a8780c78cb74/loro-1.8.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff9ab48ff7625f266ab91490f38553811965429a027f24b014a979af03a26181", size = 3504168, upload-time = "2025-09-23T15:49:26.674Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/677b02cee8ce43ca2816a68b455f6cf6d10daf36a99aec140ab5e121b57f/loro-1.8.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:fc99167626c3b0938db9172bd935bc6d72ae701b11448cd4107b71aa76a5a7ab", size = 3287927, upload-time = "2025-09-23T15:51:01.967Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/212a46bf505f7518fd235f7e3e7355e24f612cdd70cb3f9b254742bf7b9f/loro-1.8.1-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:ce7cfdb085aa9c7f6f16d9090cff64e7a8f5ccde0f11b0cb0f20567f7798c3f3", size = 3440556, upload-time = "2025-09-23T15:51:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ca/e9184f8a595add3dfa8d0a8bf44e0ae848b18e6758b4a05b9dc1f492221e/loro-1.8.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a2f8db61477b6951c6ee45b48e7c307014540229b09b17f07c6eea8ced519448", size = 3487021, upload-time = "2025-09-23T15:52:17.245Z" }, + { url = "https://files.pythonhosted.org/packages/f2/89/17dd2c68018d11bcd54e9504a9ae71d8ca520fd78c4769d54aae016946f1/loro-1.8.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:7bc42670b8cc05d7bc26525ea9f0f1404dc49c0f611ea21eb9d551d51d9f0839", size = 3384697, upload-time = "2025-09-23T15:52:53.385Z" }, ] [[package]] @@ -2495,7 +2451,7 @@ wheels = [ [[package]] name = "marimo" -version = "0.16.0" +version = "0.16.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -2517,9 +2473,9 @@ dependencies = [ { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/ad/10292a9410786135e9a9ad35297590df4fa7538161c54b3c669d5a7be999/marimo-0.16.0.tar.gz", hash = "sha256:81377a0cfe4e95f9d7341a860a0a2772caccc1e8a7bbc8d3ad20c7945d77b07c", size = 33640723, upload-time = "2025-09-18T21:20:42.815Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/62/d8def85c7ddf58a3a371eef3e553b8426e685b99a9cb8c3847c702422ec8/marimo-0.16.2.tar.gz", hash = "sha256:2bd90431846c470b4a83ded7ee8b4442854c47ad670b0aafa5fe697e54bddd5a", size = 33654187, upload-time = "2025-09-24T12:41:55.255Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/16/387627a14a18eac0556e3f9ca85b3264d120f6492cd4d3477dc32abd012b/marimo-0.16.0-py3-none-any.whl", hash = "sha256:8b7d092894a23461e087b0fdf86942876602a779b6403c01f324b35be4c99495", size = 34033289, upload-time = "2025-09-18T21:20:37.108Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0c/65a605b99486461babb6a44e516cff7cfd892a7476df8c9990e023c07625/marimo-0.16.2-py3-none-any.whl", hash = "sha256:4617864fec69a5114ce74d3200dd2f1fb38e3757afd00e0a89b2fe2b56532d09", size = 34058910, upload-time = "2025-09-24T12:41:59.721Z" }, ] [[package]] @@ -2691,7 +2647,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.14.1" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2706,9 +2662,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/e9/242096400d702924b49f8d202c6ded7efb8841cacba826b5d2e6183aef7b/mcp-1.14.1.tar.gz", hash = "sha256:31c4406182ba15e8f30a513042719c3f0a38c615e76188ee5a736aaa89e20134", size = 454944, upload-time = "2025-09-18T13:37:19.971Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/9e/e65114795f359f314d7061f4fcb50dfe60026b01b52ad0b986b4631bf8bb/mcp-1.15.0.tar.gz", hash = "sha256:5bda1f4d383cf539d3c035b3505a3de94b20dbd7e4e8b4bd071e14634eeb2d72", size = 469622, upload-time = "2025-09-25T15:39:51.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/11/d334fbb7c2aeddd2e762b86d7a619acffae012643a5738e698f975a2a9e2/mcp-1.14.1-py3-none-any.whl", hash = "sha256:3b7a479e8e5cbf5361bdc1da8bc6d500d795dc3aff44b44077a363a7f7e945a4", size = 163809, upload-time = "2025-09-18T13:37:18.165Z" }, + { url = "https://files.pythonhosted.org/packages/c9/82/4d0df23d5ff5bb982a59ad597bc7cb9920f2650278ccefb8e0d85c5ce3d4/mcp-1.15.0-py3-none-any.whl", hash = "sha256:314614c8addc67b663d6c3e4054db0a5c3dedc416c24ef8ce954e203fdc2333d", size = 166963, upload-time = "2025-09-25T15:39:50.538Z" }, ] [[package]] @@ -3180,7 +3136,7 @@ wheels = [ [[package]] name = "openai" -version = "1.108.2" +version = "1.109.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3192,9 +3148,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/ff/1dc7bec988cfbab80c2b0ee61d5178915117a4f02f68777d528219e84866/openai-1.108.2.tar.gz", hash = "sha256:e6ce793e0ef8a52d343850a5411edbd5b21214f64a8d29cdca0fcb929e8e3155", size = 563898, upload-time = "2025-09-22T23:47:38.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2d/1cb19289feef689206cd8995d7be6b5d76b7e8d64fd51438acecc11ee395/openai-1.108.2-py3-none-any.whl", hash = "sha256:4c7caa23845a49aebd8f65e01b35739b5b2df5e621d4666cbe55f0973fae81b7", size = 948390, upload-time = "2025-09-22T23:47:36.644Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, ] [[package]] @@ -4062,16 +4018,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, + { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, ] [[package]] @@ -4690,28 +4646,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, - { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, - { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, - { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, - { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, - { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, - { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, - { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" }, + { url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" }, + { url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" }, + { url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" }, + { url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" }, + { url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" }, + { url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" }, + { url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" }, + { url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" }, + { url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" }, ] [[package]] @@ -4895,15 +4851,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -5042,7 +4989,7 @@ wheels = [ [[package]] name = "testcontainers" -version = "4.13.0" +version = "4.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docker" }, @@ -5051,9 +4998,9 @@ dependencies = [ { name = "urllib3" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/e5/807161552b8bf7072d63a21d5fd3c7df54e29420e325d50b9001571fcbb6/testcontainers-4.13.0.tar.gz", hash = "sha256:ee2bc39324eeeeb710be779208ae070c8373fa9058861859203f536844b0f412", size = 77824, upload-time = "2025-09-09T13:23:49.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ce/4fd72abe8372cc8c737c62da9dadcdfb6921b57ad8932f7a0feb605e5bf5/testcontainers-4.13.1.tar.gz", hash = "sha256:4a6c5b2faa3e8afb91dff18b389a14b485f3e430157727b58e65d30c8dcde3f3", size = 77955, upload-time = "2025-09-24T22:47:47.2Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a2/ec749772b9d0fcc659b1722858f463a9cbfc7e29aca374123fb87e87fc1d/testcontainers-4.13.0-py3-none-any.whl", hash = "sha256:784292e0a3f3a4588fbbf5d6649adda81fea5fd61ad3dc73f50a7a903904aade", size = 123838, upload-time = "2025-09-09T13:23:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/cc/30/f0660686920e09680b8afb0d2738580223dbef087a9bd92f3f14163c2fa6/testcontainers-4.13.1-py3-none-any.whl", hash = "sha256:10e6013a215eba673a0bcc153c8809d6f1c53c245e0a236e3877807652af4952", size = 123995, upload-time = "2025-09-24T22:47:45.44Z" }, ] [[package]] @@ -5220,21 +5167,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/0b/293be6bc19f6da5e9b15e615a7100504f307dd4294d2c61cee3de91198e5/ty-0.0.1a21-py3-none-win_arm64.whl", hash = "sha256:21f708d02b6588323ffdbfdba38830dd0ecfd626db50aa6006b296b5470e52f9", size = 8193800, upload-time = "2025-09-19T06:54:04.583Z" }, ] -[[package]] -name = "typer" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, -] - [[package]] name = "types-toml" version = "0.10.8.20240310" @@ -5294,59 +5226,16 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.37.0" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019, upload-time = "2024-10-14T23:37:20.068Z" }, - { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898, upload-time = "2024-10-14T23:37:22.663Z" }, - { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735, upload-time = "2024-10-14T23:37:25.129Z" }, - { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126, upload-time = "2024-10-14T23:37:27.59Z" }, - { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789, upload-time = "2024-10-14T23:37:29.385Z" }, - { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523, upload-time = "2024-10-14T23:37:32.048Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, - { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, - { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] [[package]] @@ -5373,106 +5262,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, ] -[[package]] -name = "watchfiles" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, - { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, - { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, - { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, - { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, - { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, - { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, -] - [[package]] name = "websockets" version = "15.0.1"