Skip to content

Commit

Permalink
feat: cairo 1 (#1)
Browse files Browse the repository at this point in the history
This PR migrates the library from Cairo 0 to Cairo 1.
  • Loading branch information
tserg authored Dec 21, 2023
1 parent 78ef411 commit 845a543
Show file tree
Hide file tree
Showing 20 changed files with 631 additions and 795 deletions.
52 changes: 9 additions & 43 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,17 @@ on:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Python setup
uses: actions/setup-python@v3
with:
python-version: '3.9'
cache: pip
cache-dependency-path: '**/requirements.txt'

- name: Env setup
run: pip install -r requirements.txt

- name: Lint Cairo code
run: find contracts -type f -name '*.cairo' | xargs cairo-format -c

- name: Run black
run: black --check .

- name: Run flake8
run: flake8

- name: Run isort
run: isort --check-only --diff tests/

test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Python setup
uses: actions/setup-python@v3
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
python-version: '3.9'
cache: pip
cache-dependency-path: '**/requirements.txt'

- name: Env setup
run: pip install -r requirements.txt
scarb-version: "2.3.1"
- run: scarb fmt --check
- run: scarb build

- name: Run tests
run: pytest -sv -r A tests
- uses: foundry-rs/setup-snfoundry@v2
with:
starknet-foundry-version: "0.11.0"
- run: snforge test
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
__pycache__
.pytest_cache
target/

# Hypothesis
.hypothesis
.snfoundry_cache/
137 changes: 72 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,108 +1,115 @@
# Member-based access control library for Cairo

![tests](https://github.com/lindy-labs/cairo-accesscontrol/actions/workflows/tests.yml/badge.svg)
![tests](https://github.com/lindy-labs/access_control/actions/workflows/tests.yml/badge.svg)

This library is an implementation of member-based access control in Cairo for [StarkNet](https://www.cairo-lang.org/docs/), which allows an address to be assigned multiple roles using a single storage mapping.
This library implements member-based access control as a component in Cairo for [Starknet](https://www.cairo-lang.org/docs/), which allows an address to be assigned multiple roles using a single storage mapping and in a single transaction, saving on storage and transaction costs.

The design of this library was originally inspired by OpenZeppelin's [access control library](https://github.com/OpenZeppelin/cairo-contracts/tree/main/src/openzeppelin/access/accesscontrol), as well as Python's [flags](https://docs.python.org/3/library/enum.html) and Vyper's [enums](https://docs.python.org/3/library/enum.html).
The design of this library was originally inspired by OpenZeppelin's [access control library](https://github.com/OpenZeppelin/cairo-contracts), as well as Python's [flags](https://docs.python.org/3/library/enum.html) and Vyper's [enums](https://docs.vyperlang.org/en/stable/types.html#enums).

## Overview

This library uses felt values in the form of 2<sup>n</sup>, where `n` is in the range `0 <= n <= 251`, to represent user-defined roles as members.
This library uses `u128` values in the form of 2<sup>n</sup>, where `n` is in the range `0 <= n < 128`, to represent user-defined roles as members. The primary benefit of this approach is that multiple roles can be granted or revoked using a single storage variable and in a single transaction, saving on storage and transaction costs. The only drawback is that users are limited to 128 roles per contract.

Roles should be defined in a separate Cairo contract as its own namespace. For example:
Note that this access control library also relies on an admin address with superuser privileges i.e. the admin can grant or revoke any roles for any address, including the admin itself. This may introduce certain trust assumptions for the admin depending on your usage of the library.

We recommend users to define the roles in a separate Cairo file. For example:

```cairo
namespace Roles {
const MANAGER = 2 ** 0;
const STAFF = 2 ** 1;
const USER = 2 ** 2;
mod user_roles {
const MANAGER: u128 = 1;
const STAFF: u128 = 2;
const USER: u128 = 4;
}
```

Multiple roles can be represented as a single value by performing bitwise AND. Using the above example, an address can be assigned both the `MANAGER` and `STAFF` roles using a single value of 3 (equivalent to `Roles.MANAGER | Roles.STAFF` or `2 ** 0 + 2 ** 1`).
Multiple roles can be represented as a single value by performing bitwise AND. Using the above example, an address can be assigned both the `MANAGER` and `STAFF` roles using a single value of 3 (equivalent to `user_roles::MANAGER | user_roles::STAFF` or `2 ** 0 + 2 ** 1`).

Similarly, multiple roles can be granted, revoked or checked for in a single transaction using bitwise operations:
- granting role(s) is a bitwise AND operation of the currently assigned value and the value of the new role(s);
- revoking role(s) is a bitwise AND operation of the currently assigned value and the complement (bitwise NOT) of the value of the role(s) to be revoked; and
- checking for membership is a bitwise OR operation of the currently assigned value and the value of the role(s) being checked for.

Note that functions which rely on this access control library will require the `bitwise_ptr` implicit argument and `BitwiseBuiltin`.


## Usage

To use this library in a Cairo contract:
1. Include a copy of `accesscontrol_library.cairo` in your project, and import the library into the Cairo contract.
2. Define the available roles as constants in a namespace in a separate Cairo contract, and import this namespace into the Cairo contract.
To use this library, add the repository as a dependency in your `Scarb.toml`:

For example, assuming you have a `contracts/` folder with `accesscontrol_library.cairo` and `roles.cairo`, and you want to import both into a Cairo file within the same folder:
```
[dependencies]
access_control = { git = "https://github.com/lindy-labs/access_control.git" }
```

Next, define the available roles in a separate Cairo file:
```cairo
%lang starknet
from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, HashBuiltin
from contracts.accesscontrol_library import AccessControl
from contracts.roles import Roles
@view
func is_manager{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*
}(user: felt) -> (authorized: felt) {
let authorized: felt = AccessControl.has_role(Roles.MANAGER, user);
return (authorized,);
mod user_roles {
const MANAGER: u128 = 1;
const STAFF: u128 = 2;
const USER: u128 = 4;
}
```
then import both the component and the roles into your Cairo contract.

For example, assuming you have a project named `my_project` in the top-level `Scarb.toml`, and a `src/` folder with the roles defined in a `user_roles` module in `roles.cairo`:
```
use starknet::ContractAddress;
@external
func authorize{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}(
role: felt, user: felt
) {
AccessControl.assert_admin();
AccessControl._grant_role(role, user);
return ();
#[starknet::interface]
trait IMockContract<TContractState> {
fn is_manager(self: @TContractState, user: ContractAddress) -> bool;
}
@external
func manager_only_action{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() {
AccessControl.assert_has_role(Roles.MANAGER);
// Insert logic here
return ();
#[starknet::contract]
mod mock_contract {
use access_control::access_control_component;
use my_project::roles::user_roles;
use starknet::ContractAddress;
use super::IMockContract;
component!(path: access_control_component, storage: access_control, event: AccessControlEvent);
#[abi(embed_v0)]
impl AccessControlPublic = access_control_component::AccessControl<ContractState>;
impl AccessControlHelpers = access_control_component::AccessControlHelpers<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
access_control: access_control_component::Storage
}
#[event]
#[derive(Copy, Drop, starknet::Event)]
enum Event {
AccessControlEvent: access_control_component::Event
}
#[constructor]
fn constructor(ref self: ContractState, admin: ContractAddress, roles: Option<u128>) {
self.access_control.initializer(admin, roles);
}
#[abi(embed_v0)]
impl IMockContractImpl of IMockContract<ContractState> {
fn is_manager(self: @ContractState, user: ContractAddress) -> bool {
self.access_control.has_role(user_roles::MANAGER, user)
}
}
}
```

You can also refer to the test file `tests/test_accesscontrol.cairo` for another example.

We have also included a set of external and view functions in `accesscontrol_external.cairo` that you can import into your Cairo contracts.

## Development

### Set up the project
### Prerequisites

Clone the repository

```bash
git clone [email protected]:lindy-labs/cairo-accesscontrol.git
```

`cd` into it and create a Python virtual environment:

```bash
cd cairo-accesscontrol
python3 -m venv env
source env/bin/activate
```

Install dependencies:

```bash
python -m pip install -r requirements.txt
```
- [Cairo](https://github.com/starkware-libs/cairo)
- [Scarb](https://docs.swmansion.com/scarb)
- [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry)

### Run tests

To run the tests:

```bash
pytest
scarb test
```

## Formal Verification
Expand Down
14 changes: 14 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "access_control"
version = "0.1.0"
dependencies = [
"snforge_std",
]

[[package]]
name = "snforge_std"
version = "0.1.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.11.0#5465c41541c44a7804d16318fab45a2f0ccec9e7"
23 changes: 23 additions & 0 deletions Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "access_control"
version = "0.1.0"
cairo-version = "2.3.1"
authors = ["Lindy Labs"]
description = "Member-based access control component for Cairo"
readme = "README.md"
repository = "https://github.com/lindy-labs/access_control"
license-file = "LICENSE"
keywords = ["access control", "authorization", "cairo", "starknet"]

[dependencies]
starknet = "2.3.1"
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.11.0" }

[lib]

[tool.fmt]
sort-module-level-items = true
max-line-length = 120

[scripts]
test = "snforge test"
70 changes: 0 additions & 70 deletions contracts/accesscontrol_external.cairo

This file was deleted.

Loading

0 comments on commit 845a543

Please sign in to comment.