Starknet by Example

repository·main·Indexed 41 days ago

https://github.com/nethermindeth/starknetbyexample

A collection of practical code examples for developers learning to write smart contracts in Cairo for the Starknet blockchain. The documentation covers account abstraction, implementing the SNIP-6 standard account interface, and interacting with contracts using starknet.js, as well as providing guidance on using the Foundry toolkit (Forge, Cast, Anvil, and Chisel) for Ethereum application development.

Tokens
26.6K
Snippets
102
Records
145
Agent score
50%

What's inside Starknet by Example

  1. What is Foundry?

    main

    Foundry is a fast, portable, and modular toolkit for Ethereum application development written in Rust. It is composed of four primary tools:

    • Forge: An Ethereum testing framework (similar to Truffle, Hardhat, or DappTools).
    • Cast: A command-line utility for interacting with EVM smart contracts, sending transactions, and retrieving chain data.
    • Anvil: A local Ethereum node (similar to Ganache or Hardhat Network).
    • Chisel: A fast and verbose Solidity REPL (Read-Eval-Print Loop).
  2. Understand the Staking contract functionality

    main

    The Staking contract allows users to stake an ERC20 token to earn rewards in a different (or the same) ERC20 token over a specified duration.

    Core Capabilities:

    • Token Staking/Unstaking: Users stake a specific ERC20 token (defined at deployment) and can withdraw their staked tokens at any time.
    • Reward Distribution: Rewards are distributed as an ERC20 token. The amount earned is calculated based on the user's staked amount relative to the total pool and the duration of their stake.
    • Claiming: Rewards accumulate over time and can be claimed by the user at any time.
    • Dynamic Reward Rates: The reward rate is determined by the total reward tokens available over a set duration. The owner can adjust the reward rate by adding new rewards before the current period ends. New reward periods can also be initialized after a period finishes.
    • Administrative Control: Only the contract owner has the authority to set the reward amount and the reward duration.
  3. Implement a Constant Product AMM in Cairo

    main

    A Constant Product Automated Market Maker (AMM) implements the formula $x imes y = k$ to ensure the product of two token reserves remains constant during trades. This allows users to swap between two tokens or provide/remove liquidity from a pool. This implementation is a Cairo adaptation of the Solidity by Example Constant Product AMM.

    // [!include ~/listings/applications/constant_product_amm/src/contracts.cairo:ConstantProductAmmContract]
  4. What is Sierra (Safe Intermediate REpresentAtion)?

    main

    Sierra is a linear intermediate representation (IR) that bridges high-level Cairo 1 code and low-level Cairo Assembly (CASM). It is designed to ensure that programs are always provable by preventing infinite loops and invalid constraints.

    Key characteristics:

    • Safety: It prevents the compilation of non-provable programs.
    • Provability: It allows sequencers to verify transactions (including failed ones) and charge fees, mitigating DoS attacks.
    • Linearity: It uses a linear type system where values must be used exactly once.
    • Loop Handling: It transforms loops into recursion and tracks gas usage to prevent infinite execution.
  5. Understand Cairo compiler plugins

    main

    Compiler plugins in Cairo allow for the generation of additional code for specific items during the compilation process. They are used to extend the language's capabilities by automating repetitive tasks or adding new syntax/functionality.

    Core examples of built-in plugins include:

    • #[derive]: Automatically implements traits for a struct or enum.
    • #[cfg]: Enables conditional compilation based on specific flags.
    • #[generate_trait]: Generates trait implementations.
  6. Implement contract storage using the #[storage] attribute

    main

    Contract storage is implemented as a key-value store using a struct marked with the #[storage] attribute. A contract must have exactly one storage definition. This struct represents the persistent state of the contract on the blockchain, which is maintained between different contract executions.

    #[starknet::contract]
    mod MyContract {
        #[storage]
        struct Storage {
            a: u128,
            b: u8,
            c: u256,
        }
    
        // ...
    }
  7. Use stdStorage to find and write to storage slots

    main

    stdStorage is a wrapper around the record and accesses cheatcodes. It allows you to find and write to storage slots associated with specific variables without knowing the storage layout. It works by recording SLOAD and SSTORE operations during a function call.

    Key Capabilities:

    • Find Slots: Use .target(address).sig(selector).find() to locate a slot.
    • Write to Slots: Use .checked_write(value) to write to a found slot.
    • Mappings: For mappings, use .with_key(key) before calling .find().
    • Structs: For structs, use .depth(n) to specify the field depth (0 for the first field, 1 for the second, etc.).
    • Hidden Storage: It can find slots even if they are accessed via assembly (e.g., sload with a random hash).

    Caveat: While it can find slots for packed storage variables, you cannot safely write to them; execution will throw an error unless the slot is uninitialized (bytes32(0)).

    import "forge-std/Test.sol";
    
    contract TestContract is Test {
        using stdStorage for StdStorage;
    
        Storage test;
    
        function setUp() public {
            test = new Storage();
        }
    
        function testFindExists() public {
            uint256 slot = stdstore.target(address(test)).sig("exists()").find();
            assertEq(slot, 0);
        }
    
        function testWriteExists() public {
            stdstore.target(address(test)).sig("exists()").checked_write(100);
            assertEq(test.exists(), 100);
        }
    
        function testFindMapping() public {
            uint256 slot = stdstore
                .target(address(test))
                .sig(test.map_addr.selector)
                .with_key(address(this))
                .find();
            assertEq(uint(vm.load(address(test), bytes32(slot))), 1);
        }
    
        function testFindStruct() public {
            uint256 slot_for_a_field = stdstore
                .target(address(test))
                .sig(test.basicStruct.selector)
                .depth(0)
                .find();
            assertEq(uint(vm.load(address(test), bytes32(slot_for_a_field))), 1);
        }
    }
  8. Upgrade a deployed contract using replace_class_syscall

    main

    You can upgrade a deployed contract's implementation by updating its associated class hash. This is achieved by calling the replace_class_syscall function.

    Key behavior:

    • Implementation Update: The contract will now follow the logic defined in the new contract class.
    • Storage Persistence: Upgrading the class does not modify or clear the contract's existing storage. All data stored in the previous version remains intact and accessible to the new version.
  9. Handle custom types in Starknet entrypoints

    main

    When using custom types in Starknet contract entrypoints, you must manage the conversion between your high-level types and the low-level format used by the Starknet VM.

    Entrypoints require data to be in specific formats because:

    1. Input parameters are received as arrays of felt252.
    2. Return values must be converted back into arrays of felt252.
    3. Custom types require an automated way to bridge these formats.