Contracts for Cairo

A library for secure smart contract development written in Cairo for Starknet, a decentralized ZK Rollup.

This repo contains highly experimental code. Expect rapid iteration. Use at your own risk.

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 0.8.0-beta.1 (58cc88efb 2023-08-23)
cairo: 2.2.0 (https://crates.io/crates/cairo-lang-compiler/2.2.0)
sierra: 1.3.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 = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.8.0-beta.1" }
Make sure the tag matches the target release.

Basic usage

This is how it looks to build an account contract using the account module. Copy the code into src/lib.cairo.

#[starknet::contract]
mod MyAccount {
    use openzeppelin::account::Account;
    use openzeppelin::account::account::PublicKeyTrait;
    use openzeppelin::account::interface;
    use openzeppelin::introspection::interface::ISRC5;
    use starknet::account::Call;

    // Storage members used by this contract are defined in each imported
    // module whose `unsafe_state` is used. This design will be improved
    // with the addition of components in the future.
    #[storage]
    struct Storage {}

    #[constructor]
    fn constructor(ref self: ContractState, public_key: felt252) {
        let mut unsafe_state = _unsafe_state();
        Account::InternalImpl::initializer(ref unsafe_state, public_key);
    }

    #[external(v0)]
    impl SRC6Impl of interface::ISRC6<ContractState> {
        fn __execute__(self: @ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
            Account::SRC6Impl::__execute__(@_unsafe_state(), calls)
        }

        fn __validate__(self: @ContractState, mut calls: Array<Call>) -> felt252 {
            Account::SRC6Impl::__validate__(@_unsafe_state(), calls)
        }

        fn is_valid_signature(
            self: @ContractState, hash: felt252, signature: Array<felt252>
        ) -> felt252 {
            Account::SRC6Impl::is_valid_signature(@_unsafe_state(), hash, signature)
        }
    }

    #[external(v0)]
    impl SRC5Impl of ISRC5<ContractState> {
        fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
            Account::SRC5Impl::supports_interface(@_unsafe_state(), interface_id)
        }
    }

    #[external(v0)]
    impl PublicKeyImpl of PublicKeyTrait<ContractState> {
        fn get_public_key(self: @ContractState) -> felt252 {
            Account::PublicKeyImpl::get_public_key(@_unsafe_state())
        }

        fn set_public_key(ref self: ContractState, new_public_key: felt252) {
            let mut unsafe_state = _unsafe_state();
            Account::PublicKeyImpl::set_public_key(ref unsafe_state, new_public_key);
        }
    }

    #[external(v0)]
    fn __validate_deploy__(
        self: @ContractState,
        class_hash: felt252,
        contract_address_salt: felt252,
        _public_key: felt252
    ) -> felt252 {
        Account::__validate_deploy__(
            @_unsafe_state(), class_hash, contract_address_salt, _public_key
        )
    }

    #[inline(always)]
    fn _unsafe_state() -> Account::ContractState {
        Account::unsafe_new_contract_state()
    }
}

You can now compile it:

scarb build