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.
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.3.0 (f306f9a91 2023-10-23)
cairo: 2.3.0 (https://crates.io/crates/cairo-lang-compiler/2.3.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.0" }
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