Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macros package and the with_components macro #1282

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fde5105
feat: bump scarb
ericnordelo Dec 16, 2024
e311c2f
Merge branch 'feat/bump-scarb-to-2.9.2' into feat/add-macros-package
ericnordelo Dec 16, 2024
89d9ea4
feat: add crate
ericnordelo Dec 17, 2024
5bebd2d
feat: add oz_component file
ericnordelo Dec 18, 2024
b75027f
feat: add main logic
ericnordelo Jan 2, 2025
c164f00
Merge branch 'main' of github.com:OpenZeppelin/cairo-contracts into f…
ericnordelo Jan 2, 2025
acd4516
feat: add snapshot tests to macros
ericnordelo Jan 8, 2025
6814d0b
feat: format output
ericnordelo Jan 8, 2025
44c6491
feat: add warnings
ericnordelo Jan 8, 2025
72fdfeb
feat: add tests for new warnings
ericnordelo Jan 8, 2025
e81beec
feat: add snapshots
ericnordelo Jan 8, 2025
5762562
Merge branch 'main' of github.com:OpenZeppelin/cairo-contracts into f…
ericnordelo Jan 21, 2025
06705c6
feat: add more tests
ericnordelo Jan 21, 2025
d0d9faf
feat: add more tests and warnings
ericnordelo Jan 22, 2025
ba61a95
feat: remove unused dependency
ericnordelo Jan 22, 2025
81b9997
feat: add github action
ericnordelo Jan 22, 2025
0a2d5de
feat: add doc entry
ericnordelo Jan 22, 2025
b2f67e9
fix: typo
ericnordelo Jan 22, 2025
02daa77
feat: add accounts
ericnordelo Jan 22, 2025
edb6688
feat: update CHANGELOG
ericnordelo Jan 22, 2025
904b7f8
Merge branch 'main' of github.com:OpenZeppelin/cairo-contracts into f…
ericnordelo Jan 27, 2025
c58bae0
feat: test existing event struct
ericnordelo Jan 27, 2025
c8e802d
docs: finish with_components entry
ericnordelo Jan 27, 2025
e1e3db8
Update docs/modules/ROOT/pages/api/macros.adoc
ericnordelo Jan 29, 2025
8d25611
Update packages/macros/src/tests/test_with_components.rs
ericnordelo Jan 29, 2025
32754b9
Update packages/macros/src/tests/test_with_components.rs
ericnordelo Jan 29, 2025
6036d83
Update packages/macros/src/with_components.rs
ericnordelo Jan 29, 2025
455cc40
Update packages/macros/src/tests/test_with_components.rs
ericnordelo Jan 29, 2025
3c757b7
feat: apply review updates
ericnordelo Jan 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/test-macros.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Lint and test macros

on:
pull_request:
push:
branches:
- main

jobs:
test_macros:
name: Lint and test macros
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: Swatinem/rust-cache@v2

- name: Check formatting
working-directory: ./packages/macros
run: |
cargo fmt --all --check

- name: "Run linter (clippy)"
working-directory: ./packages/macros
run: |
cargo clippy --all --all-targets

- name: "Run tests"
working-directory: ./packages/macros
run: |
cargo test
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Lint and test
name: Lint and test Cairo
# This workflow runs linting and tests on all pull requests and pushes to the main branch.
# It includes Markdown linting, Cairo formatting checks, running tests with coverage,
# and uploading the coverage report to Codecov.
Expand All @@ -11,7 +11,7 @@ on:

jobs:
lint_and_test:
name: Lint and test
name: Lint and test Cairo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- The openzeppelin_macros package with the `with_components` macro (#1282)

### Changed (Breaking)

- Bump scarb to v2.9.2 (#1239)
Expand Down
4 changes: 4 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ dependencies = [
"snforge_std",
]

[[package]]
name = "openzeppelin_macros"
version = "0.20.0"

[[package]]
name = "openzeppelin_merkle_tree"
version = "0.20.0"
Expand Down
1 change: 1 addition & 0 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"packages/finance",
"packages/governance",
"packages/introspection",
"packages/macros",
"packages/merkle_tree",
"packages/presets",
"packages/security",
Expand Down
2 changes: 2 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*** xref:/guides/src5-migration.adoc[Migrating ERC165 to SRC5]
*** xref:/api/introspection.adoc[API Reference]

** xref:/api/macros.adoc[Macros]

** xref:/api/merkle-tree.adoc[Merkle Tree]

** xref:security.adoc[Security]
Expand Down
147 changes: 147 additions & 0 deletions docs/modules/ROOT/pages/api/macros.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]

= 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:

```toml
[dependencies]
openzeppelin_macros = "0.20.0"
```

== Attribute macros

[.contract]
[[with_components]]
=== `++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_
ericnordelo marked this conversation as resolved.
Show resolved Hide resolved

CAUTION: Since the macro does not expose any external implementations, developers must make sure to specify explicitly
the ones required by the contract.

[#with_components-security]
==== 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.

[#with_components-usage]
==== Usage

This is how a contract with multiple components looks when using the macro.

```cairo
#[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<ContractState>;
#[abi(embed_v0)]
impl OutsideExecutionV2Impl =
SRC9Component::OutsideExecutionV2Impl<ContractState>;

#[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<ContractState> {
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.

```cairo
#[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<ContractState>;
#[abi(embed_v0)]
impl OutsideExecutionV2Impl =
SRC9Component::OutsideExecutionV2Impl<ContractState>;

// Internal
impl AccountInternalImpl = AccountComponent::InternalImpl<ContractState>;
impl OutsideExecutionInternalImpl = SRC9Component::InternalImpl<ContractState>;
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;

#[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<ContractState> {
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
self.account.assert_only_self();
self.upgradeable.upgrade(new_class_hash);
}
}
}
```
Loading
Loading