From fde51050fdbbc3d94ccf21c3f86be794e01652a6 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 16 Dec 2024 13:47:35 +0100 Subject: [PATCH 1/9] feat: bump scarb --- CHANGELOG.md | 4 ++++ Scarb.toml | 8 ++++---- docs/modules/ROOT/pages/index.adoc | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eca7529db..b5d83bc02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed (Breaking) + +- Bump scarb to v2.9.2 (#1239) + ## 0.20.0 (2024-12-06) ### Added diff --git a/Scarb.toml b/Scarb.toml index 3a6c8722f..4ca005aa8 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -25,8 +25,8 @@ edition.workspace = true [workspace.package] version = "0.20.0" edition = "2024_07" -cairo-version = "2.9.1" -scarb-version = "2.9.1" +cairo-version = "2.9.2" +scarb-version = "2.9.2" authors = ["OpenZeppelin Community "] description = "OpenZeppelin Contracts written in Cairo for Starknet, a decentralized ZK Rollup" documentation = "https://docs.openzeppelin.com/contracts-cairo" @@ -41,8 +41,8 @@ keywords = [ ] [workspace.dependencies] -assert_macros = "2.9.1" -starknet = "2.9.1" +assert_macros = "2.9.2" +starknet = "2.9.2" snforge_std = "0.34.0" [dependencies] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 547454057..9db691184 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -21,8 +21,8 @@ before proceeding, and run the following command to check that the installation ---- $ scarb --version -scarb 2.9.1 (aba4f604a 2024-11-29) -cairo: 2.9.1 (https://crates.io/crates/cairo-lang-compiler/2.9.1) +scarb 2.9.2 (5070ff374 2024-12-11) +cairo: 2.9.2 (https://crates.io/crates/cairo-lang-compiler/2.9.2) sierra: 1.6.0 ---- From 89d9ea461ab86051e1a2435abcb446e4304cdfaa Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Tue, 17 Dec 2024 11:55:35 +0100 Subject: [PATCH 2/9] feat: add crate --- Scarb.lock | 4 ++ Scarb.toml | 1 + packages/macros/Cargo.lock | 115 ++++++++++++++++++++++++++++++++++++ packages/macros/Cargo.toml | 10 ++++ packages/macros/README.md | 1 + packages/macros/Scarb.toml | 22 +++++++ packages/macros/src/echo.rs | 6 ++ packages/macros/src/lib.rs | 1 + 8 files changed, 160 insertions(+) create mode 100644 packages/macros/Cargo.lock create mode 100644 packages/macros/Cargo.toml create mode 100644 packages/macros/README.md create mode 100644 packages/macros/Scarb.toml create mode 100644 packages/macros/src/echo.rs create mode 100644 packages/macros/src/lib.rs diff --git a/Scarb.lock b/Scarb.lock index fc9ad8b93..5a03454dd 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -76,6 +76,10 @@ dependencies = [ "snforge_std", ] +[[package]] +name = "openzeppelin_macros" +version = "0.20.0" + [[package]] name = "openzeppelin_merkle_tree" version = "0.20.0" diff --git a/Scarb.toml b/Scarb.toml index 4ca005aa8..846faeddf 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -5,6 +5,7 @@ members = [ "packages/finance", "packages/governance", "packages/introspection", + "packages/macros", "packages/merkle_tree", "packages/presets", "packages/security", diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock new file mode 100644 index 000000000..77d066930 --- /dev/null +++ b/packages/macros/Cargo.lock @@ -0,0 +1,115 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cairo-lang-macro" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda77fe9404e438edaa80c9acaf0d72260aa883ba433812d0a745f5a72f31881" +dependencies = [ + "cairo-lang-macro-attributes", + "cairo-lang-macro-stable", + "linkme", +] + +[[package]] +name = "cairo-lang-macro-attributes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32e958decd95ae122ee64daa26721da2f76e83231047f947fd9cdc5d3c90cc6" +dependencies = [ + "quote", + "scarb-stable-hash", + "syn", +] + +[[package]] +name = "cairo-lang-macro-stable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c49906d6b1c215e5814be7c5c65ecf2328898b335bee8c2409ec07cfb5530daf" + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "linkme" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openzeppelin_macros" +version = "0.20.0" +dependencies = [ + "cairo-lang-macro", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scarb-stable-hash" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1902536b23a05dd165d3992865870aaf1b0650317767cbf171ed2ca5903732a9" +dependencies = [ + "data-encoding", + "xxhash-rust", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml new file mode 100644 index 000000000..8bd72172a --- /dev/null +++ b/packages/macros/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "openzeppelin_macros" +version = "0.20.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cairo-lang-macro = "0.1" diff --git a/packages/macros/README.md b/packages/macros/README.md new file mode 100644 index 000000000..1b7d96441 --- /dev/null +++ b/packages/macros/README.md @@ -0,0 +1 @@ +## Macros \ No newline at end of file diff --git a/packages/macros/Scarb.toml b/packages/macros/Scarb.toml new file mode 100644 index 000000000..3a9847742 --- /dev/null +++ b/packages/macros/Scarb.toml @@ -0,0 +1,22 @@ +[package] +name = "openzeppelin_macros" +readme = "README.md" +keywords = [ + "openzeppelin", + "cairo", + "macro", +] +version.workspace = true +edition.workspace = true +cairo-version.workspace = true +scarb-version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +repository.workspace = true +license-file.workspace = true + +[cairo-plugin] + +[tool] +fmt.workspace = true diff --git a/packages/macros/src/echo.rs b/packages/macros/src/echo.rs new file mode 100644 index 000000000..f1356a55c --- /dev/null +++ b/packages/macros/src/echo.rs @@ -0,0 +1,6 @@ +use cairo_lang_macro::{inline_macro, ProcMacroResult, TokenStream}; + +#[inline_macro] +pub fn echo(token_stream: TokenStream) -> ProcMacroResult { + ProcMacroResult::new(TokenStream::new("println!(\"{}\", eric);".to_string())) +} diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs new file mode 100644 index 000000000..3c953a5cf --- /dev/null +++ b/packages/macros/src/lib.rs @@ -0,0 +1 @@ +mod echo; \ No newline at end of file From 5bebd2da1bc153e777f5fdbe4198a1c4d540b6bb Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 18 Dec 2024 22:51:12 +0100 Subject: [PATCH 3/9] feat: add oz_component file --- packages/macros/Cargo.lock | 714 ++++++++++++++++++++++++++++ packages/macros/Cargo.toml | 3 + packages/macros/src/echo.rs | 6 - packages/macros/src/lib.rs | 2 +- packages/macros/src/oz_component.rs | 52 ++ 5 files changed, 770 insertions(+), 7 deletions(-) delete mode 100644 packages/macros/src/echo.rs create mode 100644 packages/macros/src/oz_component.rs diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index 77d066930..35de50e2f 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -2,6 +2,82 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cairo-lang-debug" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7763505dcfe15f36899074c27185bf7e3494875f63fd06350c6e3ed8d1f91d5" +dependencies = [ + "cairo-lang-utils", +] + +[[package]] +name = "cairo-lang-diagnostics" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761d20ca9c3a3eb7025b2488aa6e0e5dc23c5d551dd95e83a989b5e87687f523" +dependencies = [ + "cairo-lang-debug", + "cairo-lang-filesystem", + "cairo-lang-utils", + "itertools", +] + +[[package]] +name = "cairo-lang-filesystem" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9dc486c554e2df3be8e84c47e30fe55b59d2349b680fbe992bfba801ef93ff5" +dependencies = [ + "cairo-lang-debug", + "cairo-lang-utils", + "path-clean", + "rust-analyzer-salsa", + "semver", + "serde", + "smol_str", + "toml", +] + [[package]] name = "cairo-lang-macro" version = "0.1.1" @@ -30,12 +106,184 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c49906d6b1c215e5814be7c5c65ecf2328898b335bee8c2409ec07cfb5530daf" +[[package]] +name = "cairo-lang-parser" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e2b488f659432c8b866bf540e54ab3696a24ac0f366faac33b860c5313e78c" +dependencies = [ + "cairo-lang-diagnostics", + "cairo-lang-filesystem", + "cairo-lang-syntax", + "cairo-lang-syntax-codegen", + "cairo-lang-utils", + "colored", + "itertools", + "num-bigint", + "num-traits", + "rust-analyzer-salsa", + "smol_str", + "unescaper", +] + +[[package]] +name = "cairo-lang-primitive-token" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123ac0ecadf31bacae77436d72b88fa9caef2b8e92c89ce63a125ae911a12fae" + +[[package]] +name = "cairo-lang-syntax" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a38f1431f22a9487b9b0dd7aef098c9605fe6b8677e0f620547aa69195f7fb5" +dependencies = [ + "cairo-lang-debug", + "cairo-lang-filesystem", + "cairo-lang-primitive-token", + "cairo-lang-utils", + "num-bigint", + "num-traits", + "rust-analyzer-salsa", + "smol_str", + "unescaper", +] + +[[package]] +name = "cairo-lang-syntax-codegen" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7990586c9bb37eaa875ffeb218bdecf96f87881d03263ebf84fcd46514ca9f" +dependencies = [ + "genco", + "xshell", +] + +[[package]] +name = "cairo-lang-utils" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff5d7609abc99c15de7d7f90b8441b27e2bd52e930a3014c95a9b620e5d3211a" +dependencies = [ + "hashbrown 0.14.5", + "indexmap", + "itertools", + "num-bigint", + "num-traits", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "genco" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35958104272e516c2a5f66a9d82fba4784d2b585fc1e2358b8f96e15d342995" +dependencies = [ + "genco-macros", + "relative-path", + "smallvec", +] + +[[package]] +name = "genco-macros" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + [[package]] name = "linkme" version = "0.3.31" @@ -56,13 +304,108 @@ dependencies = [ "syn", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "openzeppelin_macros" version = "0.20.0" dependencies = [ "cairo-lang-macro", + "cairo-lang-parser", + "cairo-lang-syntax", + "regex", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "path-clean" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + [[package]] name = "proc-macro2" version = "1.0.92" @@ -81,6 +424,85 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rust-analyzer-salsa" +version = "0.17.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719825638c59fd26a55412a24561c7c5bcf54364c88b9a7a04ba08a6eafaba8d" +dependencies = [ + "indexmap", + "lock_api", + "oorandom", + "parking_lot", + "rust-analyzer-salsa-macros", + "rustc-hash", + "smallvec", + "tracing", + "triomphe", +] + +[[package]] +name = "rust-analyzer-salsa-macros" +version = "0.17.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d96498e9684848c6676c399032ebc37c52da95ecbefa83d71ccc53b9f8a4a8e" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "scarb-stable-hash" version = "1.0.0" @@ -91,6 +513,71 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" version = "2.0.90" @@ -102,14 +589,241 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triomphe" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "xshell" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" + [[package]] name = "xxhash-rust" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 8bd72172a..5dd2b53b7 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -8,3 +8,6 @@ crate-type = ["cdylib"] [dependencies] cairo-lang-macro = "0.1" +cairo-lang-parser = "2.9" +cairo-lang-syntax = "2.9" +regex = "1.11.1" diff --git a/packages/macros/src/echo.rs b/packages/macros/src/echo.rs deleted file mode 100644 index f1356a55c..000000000 --- a/packages/macros/src/echo.rs +++ /dev/null @@ -1,6 +0,0 @@ -use cairo_lang_macro::{inline_macro, ProcMacroResult, TokenStream}; - -#[inline_macro] -pub fn echo(token_stream: TokenStream) -> ProcMacroResult { - ProcMacroResult::new(TokenStream::new("println!(\"{}\", eric);".to_string())) -} diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 3c953a5cf..1bdaa6ace 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1 +1 @@ -mod echo; \ No newline at end of file +mod oz_component; diff --git a/packages/macros/src/oz_component.rs b/packages/macros/src/oz_component.rs new file mode 100644 index 000000000..d9bdc63ba --- /dev/null +++ b/packages/macros/src/oz_component.rs @@ -0,0 +1,52 @@ +use cairo_lang_macro::{attribute_macro, Diagnostic, Diagnostics, ProcMacroResult, TokenStream}; +use regex::Regex; + +/// List of the accepted components. +const OZ_COMPONENTS: [&str; 1] = ["ERC20"]; + +/// Inserts a component dependency into a modules codebase. +#[attribute_macro] +pub fn oz_component(attribute_stream: TokenStream, item_stream: TokenStream) -> ProcMacroResult { + let args = parse_args(&attribute_stream.to_string()); + + if args.len() != 1 { + return ProcMacroResult::new(TokenStream::empty()) + .with_diagnostics(Diagnostic::error("Invalid number of arguments").into()); + } + + ProcMacroResult::new(TokenStream::empty()) +} + +/// Parses the arguments from the attribute stream. +fn parse_args(text: &str) -> Vec { + let re = Regex::new(r"(\w+)").unwrap(); + let matches = re.find_iter(text); + matches.map(|m| m.as_str().to_string()).collect() +} + +fn get_component_info(name: &str) -> (ComponentInfo, Diagnostics) { + match name { + "ERC20" => ComponentInfo { + name: name.to_string(), + path: format!("openzeppelin_token::erc20::ERC20Component"), + storage: format!("ERC20Storage"), + event: format!("ERC20Event"), + }, + _ => panic!("Invalid component name"), + } +} + +/// Information about a component. +/// +/// # Members +/// +/// * `name` - The name of the component (e.g. `ERC20Component`) +/// * `path` - The path from where the component is imported (e.g. `openzeppelin_token::erc20::ERC20Component`) +/// * `storage` - The path to reference the component in storage (e.g. `ERC20Storage`) +/// * `event` - The path to reference the component events (e.g. `ERC20Event`) +pub struct ComponentInfo { + pub name: String, + pub path: String, + pub storage: String, + pub event: String, +} From b75027f1376b67155ac734117f544b2dd530425f Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Thu, 2 Jan 2025 18:03:00 +0100 Subject: [PATCH 4/9] feat: add main logic --- packages/macros/Cargo.lock | 41 ++- packages/macros/Cargo.toml | 3 + packages/macros/src/constants.rs | 8 + packages/macros/src/lib.rs | 4 +- packages/macros/src/oz_component.rs | 52 --- packages/macros/src/utils.rs | 7 + packages/macros/src/with_components.rs | 468 +++++++++++++++++++++++++ 7 files changed, 527 insertions(+), 56 deletions(-) create mode 100644 packages/macros/src/constants.rs delete mode 100644 packages/macros/src/oz_component.rs create mode 100644 packages/macros/src/utils.rs create mode 100644 packages/macros/src/with_components.rs diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index 35de50e2f..fb8a8e888 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -50,6 +50,23 @@ dependencies = [ "cairo-lang-utils", ] +[[package]] +name = "cairo-lang-defs" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d29dc5a3cafe94ea4397d41b00cd54a9dffbe9bc3a3092a9ea617ea737bc6e" +dependencies = [ + "cairo-lang-debug", + "cairo-lang-diagnostics", + "cairo-lang-filesystem", + "cairo-lang-parser", + "cairo-lang-syntax", + "cairo-lang-utils", + "itertools 0.12.1", + "rust-analyzer-salsa", + "smol_str", +] + [[package]] name = "cairo-lang-diagnostics" version = "2.9.2" @@ -59,7 +76,7 @@ dependencies = [ "cairo-lang-debug", "cairo-lang-filesystem", "cairo-lang-utils", - "itertools", + "itertools 0.12.1", ] [[package]] @@ -118,7 +135,7 @@ dependencies = [ "cairo-lang-syntax-codegen", "cairo-lang-utils", "colored", - "itertools", + "itertools 0.12.1", "num-bigint", "num-traits", "rust-analyzer-salsa", @@ -167,7 +184,7 @@ checksum = "ff5d7609abc99c15de7d7f90b8441b27e2bd52e930a3014c95a9b620e5d3211a" dependencies = [ "hashbrown 0.14.5", "indexmap", - "itertools", + "itertools 0.12.1", "num-bigint", "num-traits", "serde", @@ -263,6 +280,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "itertools" version = "0.12.1" @@ -272,6 +295,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -365,9 +397,12 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" name = "openzeppelin_macros" version = "0.20.0" dependencies = [ + "cairo-lang-defs", "cairo-lang-macro", "cairo-lang-parser", "cairo-lang-syntax", + "indoc", + "itertools 0.14.0", "regex", ] diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 5dd2b53b7..5f4cb0763 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -10,4 +10,7 @@ crate-type = ["cdylib"] cairo-lang-macro = "0.1" cairo-lang-parser = "2.9" cairo-lang-syntax = "2.9" +cairo-lang-defs = "2.9" +indoc = "2.0.5" regex = "1.11.1" +itertools = "0.14.0" diff --git a/packages/macros/src/constants.rs b/packages/macros/src/constants.rs new file mode 100644 index 000000000..fc43a646c --- /dev/null +++ b/packages/macros/src/constants.rs @@ -0,0 +1,8 @@ +pub const TAB: &str = " "; + +pub const CONTRACT_ATTRIBUTE: &str = "starknet::contract"; +pub const CONSTRUCTOR_ATTRIBUTE: &str = "constructor"; +pub const STORAGE_STRUCT_NAME: &str = "Storage"; +pub const EVENT_ENUM_NAME: &str = "Event"; +pub const SUBSTORAGE_ATTRIBUTE: &str = "substorage(v0)"; +pub const FLAT_ATTRIBUTE: &str = "flat"; diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index 1bdaa6ace..d1467492c 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1 +1,3 @@ -mod oz_component; +mod constants; +mod utils; +mod with_components; diff --git a/packages/macros/src/oz_component.rs b/packages/macros/src/oz_component.rs deleted file mode 100644 index d9bdc63ba..000000000 --- a/packages/macros/src/oz_component.rs +++ /dev/null @@ -1,52 +0,0 @@ -use cairo_lang_macro::{attribute_macro, Diagnostic, Diagnostics, ProcMacroResult, TokenStream}; -use regex::Regex; - -/// List of the accepted components. -const OZ_COMPONENTS: [&str; 1] = ["ERC20"]; - -/// Inserts a component dependency into a modules codebase. -#[attribute_macro] -pub fn oz_component(attribute_stream: TokenStream, item_stream: TokenStream) -> ProcMacroResult { - let args = parse_args(&attribute_stream.to_string()); - - if args.len() != 1 { - return ProcMacroResult::new(TokenStream::empty()) - .with_diagnostics(Diagnostic::error("Invalid number of arguments").into()); - } - - ProcMacroResult::new(TokenStream::empty()) -} - -/// Parses the arguments from the attribute stream. -fn parse_args(text: &str) -> Vec { - let re = Regex::new(r"(\w+)").unwrap(); - let matches = re.find_iter(text); - matches.map(|m| m.as_str().to_string()).collect() -} - -fn get_component_info(name: &str) -> (ComponentInfo, Diagnostics) { - match name { - "ERC20" => ComponentInfo { - name: name.to_string(), - path: format!("openzeppelin_token::erc20::ERC20Component"), - storage: format!("ERC20Storage"), - event: format!("ERC20Event"), - }, - _ => panic!("Invalid component name"), - } -} - -/// Information about a component. -/// -/// # Members -/// -/// * `name` - The name of the component (e.g. `ERC20Component`) -/// * `path` - The path from where the component is imported (e.g. `openzeppelin_token::erc20::ERC20Component`) -/// * `storage` - The path to reference the component in storage (e.g. `ERC20Storage`) -/// * `event` - The path to reference the component events (e.g. `ERC20Event`) -pub struct ComponentInfo { - pub name: String, - pub path: String, - pub storage: String, - pub event: String, -} diff --git a/packages/macros/src/utils.rs b/packages/macros/src/utils.rs new file mode 100644 index 000000000..f7812e9d8 --- /dev/null +++ b/packages/macros/src/utils.rs @@ -0,0 +1,7 @@ +use crate::constants::TAB; +use std::iter::repeat; + +/// Generates a string with `n` tabs. +pub fn tabs(n: usize) -> String { + repeat(TAB).take(n).collect() +} diff --git a/packages/macros/src/with_components.rs b/packages/macros/src/with_components.rs new file mode 100644 index 000000000..2e510052a --- /dev/null +++ b/packages/macros/src/with_components.rs @@ -0,0 +1,468 @@ +use crate::constants::{ + CONSTRUCTOR_ATTRIBUTE, CONTRACT_ATTRIBUTE, EVENT_ENUM_NAME, FLAT_ATTRIBUTE, + STORAGE_STRUCT_NAME, SUBSTORAGE_ATTRIBUTE, +}; +use crate::utils::tabs; +use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; +use cairo_lang_macro::{attribute_macro, Diagnostic, Diagnostics, ProcMacroResult, TokenStream}; +use cairo_lang_parser::utils::SimpleParserDatabase; +use cairo_lang_syntax::node::ast::MaybeModuleBody; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::helpers::{BodyItems, QueryAttrs}; +use cairo_lang_syntax::node::{ast, SyntaxNode, Terminal, TypedSyntaxNode}; +use indoc::{formatdoc, indoc}; +use itertools::Itertools; +use regex::Regex; + +const ALLOWED_COMPONENTS: [&str; 2] = ["ERC20", "Ownable"]; + +/// Inserts multiple component dependencies into a modules codebase. +#[attribute_macro] +pub fn with_components(attribute_stream: TokenStream, item_stream: TokenStream) -> ProcMacroResult { + let args = parse_args(&attribute_stream.to_string()); + + // 1. Get the components info (if valid) + let mut components_info = vec![]; + for arg in args { + let (component_info, diagnostics) = get_component_info(&arg); + if component_info.is_none() { + return ProcMacroResult::new(TokenStream::empty()).with_diagnostics(diagnostics); + } + components_info.push(component_info.unwrap()); + } + + // 2. Parse the item stream + let db = SimpleParserDatabase::default(); + let parsed = db.parse_virtual(item_stream.to_string()); + if parsed.is_err() { + let error_message = parsed.err().unwrap().format(&db); + let error = Diagnostic::error(error_message); + return ProcMacroResult::new(TokenStream::empty()).with_diagnostics(error.into()); + } + + // 3. Build the patch + let node = parsed.unwrap(); + let (content, diagnostics) = build_patch(&db, node, components_info); + + println!("\n\ncontent: {}\n\n", content); + + ProcMacroResult::new(TokenStream::new(content)).with_diagnostics(diagnostics) +} + +/// Parses the arguments from the attribute stream. +fn parse_args(text: &str) -> Vec { + let re = Regex::new(r"(\w+)").unwrap(); + let matches = re.find_iter(text); + matches.map(|m| m.as_str().to_string()).collect() +} + +/// Builds the patch for a given node and component info. +fn build_patch( + db: &dyn SyntaxGroup, + node: SyntaxNode, + components_info: Vec, +) -> (String, Diagnostics) { + let mut builder = PatchBuilder::new_ex(db, &node); + + let typed = ast::SyntaxFile::from_syntax_node(db, node); + let mut base_rnode = RewriteNode::from_ast(&typed); + let module_rnode = base_rnode + .modify_child(db, ast::SyntaxFile::INDEX_ITEMS) + .modify_child(db, 0); + + // Validate the contract module + let (errors, warnings) = validate_contract_module(db, module_rnode, &components_info); + if errors.len() > 0 { + return (String::new(), errors.into()); + } + + let mut body_rnode = module_rnode.modify_child(db, ast::ItemModule::INDEX_BODY); + + process_module_items(&mut body_rnode, db, &components_info); + add_use_clauses_and_macros(&mut body_rnode, db, &components_info); + + builder.add_modified(base_rnode); + let (content, _) = builder.build(); + + (content, warnings.into()) +} + +/// Validates that the contract module: +/// +/// - Has the `#[starknet::contract]` attribute. +/// - Has a constructor calling the corresponding initializers. +/// +/// NOTE: Missing initializers are added as Warnings. +/// +/// # Returns +/// +/// * `errors` - The errors that arose during the validation. +/// * `warnings` - The warnings that arose during the validation. +fn validate_contract_module( + db: &dyn SyntaxGroup, + node: &mut RewriteNode, + components_info: &Vec, +) -> (Vec, Vec) { + if let RewriteNode::Copied(copied) = node { + let item = ast::ItemModule::from_syntax_node(db, copied.clone()); + + // 1. Check that the module has a body (error) + let MaybeModuleBody::Some(body) = item.body(db) else { + let error = Diagnostic::error("Contract module must have a body"); + return (vec![error], vec![]); + }; + + // 2. Check that the module has the `#[starknet::contract]` attribute (error) + if !item.has_attr(db, CONTRACT_ATTRIBUTE) { + let error = Diagnostic::error( + "Contract module must have the `#[starknet::contract]` attribute", + ); + return (vec![error], vec![]); + } + + // 3. Check that the module has the corresponding initializers (warning) + let components_with_initializer = components_info + .iter() + .filter(|c| c.has_initializer) + .collect::>(); + if components_with_initializer.len() > 0 { + // Check that the constructor is present + let Some(constructor) = body.items_vec(db).into_iter().find(|item| { + matches!(item, ast::ModuleItem::FreeFunction(function_ast) if function_ast.has_attr(db, CONSTRUCTOR_ATTRIBUTE)) + }) else { + let components_with_initializer_str = components_with_initializer.iter().map(|c| c.short_name()).join(", "); + let warning = Diagnostic::warn(formatdoc! {" + It looks like the initilizers for the following components are missing: + + {components_with_initializer_str} + + This may lead to unexpected behavior. We recommend adding a constructor with the corresponding initializer calls. + "}); + return (vec![], vec![warning]); + }; + + // Get the constructor code (maybe we can do this without the builder) + let constructor_ast = constructor.as_syntax_node(); + let typed = ast::ModuleItem::from_syntax_node(db, constructor_ast.clone()); + let constructor_rnode = RewriteNode::from_ast(&typed); + + let mut builder = PatchBuilder::new_ex(db, &constructor_ast); + builder.add_modified(constructor_rnode); + let (constructor_code, _) = builder.build(); + + let mut components_with_initializer_missing = vec![]; + for component in components_with_initializer.iter() { + if !constructor_code.contains(&format!("self.{}.initializer(", component.storage)) { + components_with_initializer_missing.push(component.short_name()); + } + } + + let components_with_initializer_missing_str = + components_with_initializer_missing.join(", "); + if components_with_initializer_missing.len() > 0 { + let warning = Diagnostic::warn(formatdoc! {" + It looks like the initilizers for the following components are missing: + + {components_with_initializer_missing_str} + + This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. + "}); + return (vec![], vec![warning]); + } + } + } + + (vec![], vec![]) +} + +/// Iterates over the items in the body node and processes them. +fn process_module_items( + body_rnode: &mut RewriteNode, + db: &dyn SyntaxGroup, + components_info: &Vec, +) { + let items_rnode = body_rnode.modify_child(db, ast::ModuleBody::INDEX_ITEMS); + let items_mnode = items_rnode.modify(db); + let mut event_enum_found = false; + + for item_rnode in items_mnode.children.as_mut().unwrap() { + if let RewriteNode::Copied(copied) = item_rnode { + let item = ast::ModuleItem::from_syntax_node(db, copied.clone()); + + match item { + ast::ModuleItem::Struct(item_struct) + if item_struct.name(db).text(db) == STORAGE_STRUCT_NAME => + { + process_storage_struct(item_rnode, db, components_info); + } + ast::ModuleItem::Enum(item_enum) + if item_enum.name(db).text(db) == EVENT_ENUM_NAME => + { + process_event_enum(item_rnode, db, components_info); + event_enum_found = true; + } + _ => {} + } + } + } + + // If the event enum is not found, add it. + if !event_enum_found { + add_event_enum(body_rnode, db, components_info); + } +} + +/// Modifies the storage struct to add the component entries. +fn process_storage_struct( + item_struct: &mut RewriteNode, + db: &dyn SyntaxGroup, + components_info: &Vec, +) { + let item_struct_mnode = item_struct.modify(db); + let item_struct_children = item_struct_mnode.children.as_mut().unwrap(); + let components_rnode = + ComponentsGenerationData(components_info).generate_for_storage_struct(db); + + // Insert the components at the beginning of the struct body. + item_struct_children.insert(ast::ItemStruct::INDEX_LBRACE + 1, components_rnode); +} + +/// Modifies the event enum to add the component events. +fn process_event_enum( + item_enum: &mut RewriteNode, + db: &dyn SyntaxGroup, + components_info: &Vec, +) { + let item_enum_mnode = item_enum.modify(db); + let item_enum_children = item_enum_mnode.children.as_mut().unwrap(); + let components_rnode = ComponentsGenerationData(components_info).generate_for_event_enum(db); + + // Insert the components at the beginning of the enum body. + item_enum_children.insert(ast::ItemEnum::INDEX_LBRACE + 1, components_rnode); +} + +fn add_event_enum( + body_rnode: &mut RewriteNode, + db: &dyn SyntaxGroup, + components_info: &Vec, +) { + let body_mnode = body_rnode.modify(db); + let event_enum_rnode = ComponentsGenerationData(components_info).generate_event_enum(db); + + // It is safe to unwrap here because we know that the node has at least the storage struct children + body_mnode + .children + .as_mut() + .unwrap() + .insert(ast::ModuleBody::INDEX_RBRACE, event_enum_rnode); +} + +/// Modifies the body node to add the use clauses and the `component!` macros to the module. +fn add_use_clauses_and_macros( + body_rnode: &mut RewriteNode, + db: &dyn SyntaxGroup, + components_info: &Vec, +) { + let body_mnode = body_rnode.modify(db); + let components_rnode = ComponentsGenerationData(components_info).generate_for_module(db); + + // It is safe to unwrap here because we know that the node has at least the storage struct children + body_mnode + .children + .as_mut() + .unwrap() + .insert(ast::ModuleBody::INDEX_RBRACE, components_rnode); +} + +/// Information about a component. +/// +/// # Members +/// +/// * `name` - The name of the component (e.g. `ERC20Component`) +/// * `path` - The path from where the component is imported (e.g. `openzeppelin_token::erc20::ERC20Component`) +/// * `storage` - The path to reference the component in storage (e.g. `erc20`) +/// * `event` - The path to reference the component events (e.g. `ERC20Event`) +/// * `has_initializer` - Whether the component requires an initializer (e.g. `true`) +/// * `internal_impls` - The internal implementations of the component to be added to +/// the module by default (e.g. `["InternalImpl1", "InternalImpl2"]`) +#[derive(Debug, Clone)] +pub struct ComponentInfo { + pub name: String, + pub path: String, + pub storage: String, + pub event: String, + pub has_initializer: bool, + pub internal_impls: Vec, +} + +impl ComponentInfo { + fn short_name(&self) -> String { + self.name + .split("Component") + .next() + .expect("Component name must end with 'Component'") + .to_string() + } +} + +/// Returns the component info for a given component name. +/// +/// # Arguments +/// +/// * `name` - The name of the component (e.g. `ERC20`). +/// +/// Allowed components are: +/// `ERC20`, `Ownable` +fn get_component_info(name: &str) -> (Option, Diagnostics) { + let info = match name { + "ERC20" => Some(ComponentInfo { + name: format!("ERC20Component"), + path: format!("openzeppelin_token::erc20::ERC20Component"), + storage: format!("erc20"), + event: format!("ERC20Event"), + has_initializer: true, + internal_impls: vec!["InternalImpl".to_string()], + }), + "Ownable" => Some(ComponentInfo { + name: format!("OwnableComponent"), + path: format!("openzeppelin_access::ownable::OwnableComponent"), + storage: format!("ownable"), + event: format!("OwnableEvent"), + has_initializer: true, + internal_impls: vec!["InternalImpl".to_string()], + }), + _ => None, + }; + if info.is_none() { + let allowed_components = ALLOWED_COMPONENTS.join(", "); + let error_message = formatdoc! {" + Invalid component: {name} + + Allowed components are: + {allowed_components} + "}; + let error = Diagnostic::error(error_message); + return (None, error.into()); + } + + (info, Diagnostics::new(vec![])) +} + +/// Set of component information to be used for code generation. +struct ComponentsGenerationData<'a>(&'a Vec); + +impl ComponentsGenerationData<'_> { + fn generate_for_module(self, _db: &dyn SyntaxGroup) -> RewriteNode { + RewriteNode::interpolate_patched( + indoc! {" + + $component_use_clause_entries$ + + $component_macro_entries$ + + $component_internal_impls_entries$ + "}, + &[ + ( + "component_use_clause_entries".to_string(), + self.component_use_clause_entries(), + ), + ( + "component_macro_entries".to_string(), + self.component_macro_entries(), + ), + ( + "component_internal_impls_entries".to_string(), + self.component_internal_impls_entries(), + ), + ] + .into(), + ) + } + + fn generate_for_storage_struct(self, _db: &dyn SyntaxGroup) -> RewriteNode { + let mut entries = vec![]; + for component in self.0.iter() { + entries.push(format!("{}#[{}]", tabs(2), SUBSTORAGE_ATTRIBUTE)); + entries.push(format!( + "{}{}: {}::Storage,", + tabs(2), + component.storage, + component.name + )); + } + RewriteNode::Text(entries.join("\n") + "\n") + } + + fn generate_for_event_enum(self, _db: &dyn SyntaxGroup) -> RewriteNode { + let mut entries = vec![]; + for component in self.0.iter() { + entries.push(format!("{}#[{}]", tabs(2), FLAT_ATTRIBUTE)); + entries.push(format!( + "{}{}: {}::Event,", + tabs(2), + component.event, + component.name + )); + } + RewriteNode::Text(entries.join("\n") + "\n") + } + + fn generate_event_enum(self, _db: &dyn SyntaxGroup) -> RewriteNode { + let mut entries = vec![]; + + entries.push(format!("\n{}#[event]", tabs(1))); + entries.push(format!("{}#[derive(Drop, starknet::Event)]", tabs(1))); + entries.push(format!("{}enum {} {{", tabs(1), EVENT_ENUM_NAME)); + for component in self.0.iter() { + entries.push(format!("{}#[{}]", tabs(2), FLAT_ATTRIBUTE)); + entries.push(format!( + "{}{}: {}::Event,", + tabs(2), + component.event, + component.name + )); + } + entries.push(format!("{}}}", tabs(1))); + RewriteNode::Text(entries.join("\n")) + } + + fn component_use_clause_entries(&self) -> RewriteNode { + let mut entries = vec![]; + for component in self.0.iter() { + entries.push(format!("{}use {};", tabs(1), component.path)); + } + RewriteNode::Text(entries.join("\n")) + } + + fn component_macro_entries(&self) -> RewriteNode { + let mut entries = vec![]; + for component in self.0.iter() { + entries.push(format!( + "{}component!(path: {}, storage: {}, event: {});", + tabs(1), + component.name, + component.storage, + component.event + )); + } + RewriteNode::Text(entries.join("\n")) + } + + fn component_internal_impls_entries(&self) -> RewriteNode { + let mut entries = vec![]; + for component in self.0.iter() { + for implementation in component.internal_impls.iter() { + entries.push(format!( + "{}impl {}{} = {}::{};", + tabs(1), + component.short_name(), + implementation, + component.name, + implementation + )); + } + } + RewriteNode::Text(entries.join("\n")) + } +} From acd45164c04e057e80d741f401018d746464610e Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 8 Jan 2025 14:12:32 +0100 Subject: [PATCH 5/9] feat: add snapshot tests to macros --- packages/macros/Cargo.lock | 43 ++++ packages/macros/Cargo.toml | 1 + packages/macros/src/lib.rs | 9 +- packages/macros/src/tests/mod.rs | 1 + ...sts__test_with_components__with_erc20.snap | 43 ++++ ...components__with_erc20_no_initializer.snap | 48 ++++ ...s__test_with_components__with_no_body.snap | 18 ++ ...omponents__with_no_contract_attribute.snap | 18 ++ ...s__test_with_components__with_ownable.snap | 42 +++ ...mponents__with_ownable_no_initializer.snap | 47 ++++ ..._with_components__with_two_components.snap | 51 ++++ ...s__with_two_components_no_constructor.snap | 51 ++++ ...s__with_two_components_no_initializer.snap | 55 ++++ .../macros/src/tests/test_with_components.rs | 240 ++++++++++++++++++ packages/macros/src/with_components.rs | 32 ++- 15 files changed, 690 insertions(+), 9 deletions(-) create mode 100644 packages/macros/src/tests/mod.rs create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_body.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_contract_attribute.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap create mode 100644 packages/macros/src/tests/test_with_components.rs diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index fb8a8e888..691e9e1f5 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -206,6 +206,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -218,6 +230,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.1" @@ -286,6 +304,18 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "insta" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" +dependencies = [ + "console", + "linked-hash-map", + "once_cell", + "similar", +] + [[package]] name = "itertools" version = "0.12.1" @@ -316,6 +346,12 @@ version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linkme" version = "0.3.31" @@ -402,6 +438,7 @@ dependencies = [ "cairo-lang-parser", "cairo-lang-syntax", "indoc", + "insta", "itertools 0.14.0", "regex", ] @@ -592,6 +629,12 @@ dependencies = [ "serde", ] +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "smallvec" version = "1.13.2" diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 5f4cb0763..8c7cd4f99 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -14,3 +14,4 @@ cairo-lang-defs = "2.9" indoc = "2.0.5" regex = "1.11.1" itertools = "0.14.0" +insta = "1.42.0" diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index d1467492c..0aae251ba 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -1,3 +1,6 @@ -mod constants; -mod utils; -mod with_components; +pub(crate) mod constants; +pub(crate) mod utils; +pub(crate) mod with_components; + +#[cfg(test)] +mod tests; diff --git a/packages/macros/src/tests/mod.rs b/packages/macros/src/tests/mod.rs new file mode 100644 index 000000000..c991cb7f2 --- /dev/null +++ b/packages/macros/src/tests/mod.rs @@ -0,0 +1 @@ +mod test_with_components; diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap new file mode 100644 index 000000000..40ea203d5 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap @@ -0,0 +1,43 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + erc20: ERC20Component::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer("MyToken", "MTK"); + } + + use openzeppelin_token::erc20::ERC20Component; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + }} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap new file mode 100644 index 000000000..cb026ac50 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap @@ -0,0 +1,48 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + erc20: ERC20Component::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState) { + } + + use openzeppelin_token::erc20::ERC20Component; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + }} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +ERC20 + +This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_body.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_body.snap new file mode 100644 index 000000000..fbb5f51a5 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_body.snap @@ -0,0 +1,18 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +None + +Diagnostics: + +==== +Error: Contract module must have a body. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_contract_attribute.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_contract_attribute.snap new file mode 100644 index 000000000..2b1a5437e --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_no_contract_attribute.snap @@ -0,0 +1,18 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +None + +Diagnostics: + +==== +Error: Contract module must have the `#[starknet::contract]` attribute. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap new file mode 100644 index 000000000..2dfbe9ba7 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap @@ -0,0 +1,42 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod Owned { + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + ownable: OwnableComponent::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + }} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap new file mode 100644 index 000000000..d1fe3d228 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap @@ -0,0 +1,47 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod Owned { + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + ownable: OwnableComponent::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState) { + } + + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + }} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +Ownable + +This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap new file mode 100644 index 000000000..cabc6f8f3 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap @@ -0,0 +1,51 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + self.erc20.initializer("MyToken", "MTK"); + } + + use openzeppelin_token::erc20::ERC20Component; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + }} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap new file mode 100644 index 000000000..2c1ad4c20 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap @@ -0,0 +1,51 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, +} + + use openzeppelin_token::erc20::ERC20Component; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + }} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +ERC20, Ownable + +This may lead to unexpected behavior. We recommend adding a constructor with the corresponding initializer calls. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap new file mode 100644 index 000000000..cfec56055 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap @@ -0,0 +1,55 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, +} + + #[constructor] + fn constructor(ref self: ContractState) { + } + + use openzeppelin_token::erc20::ERC20Component; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + }} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +ERC20, Ownable + +This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs new file mode 100644 index 000000000..d96159e8d --- /dev/null +++ b/packages/macros/src/tests/test_with_components.rs @@ -0,0 +1,240 @@ +use crate::with_components::with_components_avevetedp5blk as with_components; +use cairo_lang_macro::TokenStream; +use indoc::{formatdoc, indoc}; +use insta::assert_snapshot; + +#[test] +fn test_with_erc20() { + let attribute = "(ERC20)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer(\"MyToken\", \"MTK\"); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_erc20_no_initializer() { + let attribute = "(ERC20)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_ownable() { + let attribute = "(Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod Owned { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_ownable_no_initializer() { + let attribute = "(Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod Owned { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_two_components() { + let attribute = "(ERC20, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + self.erc20.initializer(\"MyToken\", \"MTK\"); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_two_components_no_initializer() { + let attribute = "(ERC20, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_two_components_no_constructor() { + let attribute = "(ERC20, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_no_contract_attribute() { + let attribute = "(Ownable)"; + let item = indoc!( + " + pub mod Owned { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_no_body() { + let attribute = "(ERC20, Ownable)"; + let item = indoc!( + " + pub mod MyToken; + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +// +// Helpers +// + +/// Returns a string representation of the result of the macro expansion, +/// including the token stream, diagnostics and aux data. +fn get_string_result(attribute: &str, item: &str) -> String { + let attribute_stream = TokenStream::new(attribute.to_string()); + let item_stream = TokenStream::new(item.to_string()); + let raw_result = with_components(attribute_stream, item_stream); + let none = "None"; + + let mut token_stream = raw_result.token_stream.to_string(); + let mut diagnostics = String::new(); + for d in raw_result.diagnostics { + diagnostics += format!("====\n{:?}: {}====", d.severity, d.message).as_str(); + } + + if token_stream.is_empty() { + token_stream = none.to_string(); + } + if diagnostics.is_empty() { + diagnostics = none.to_string(); + } + + formatdoc! { + " + TokenStream: + + {} + + Diagnostics: + + {} + + AuxData: + + {:?} + ", + token_stream, diagnostics, raw_result.aux_data + } +} diff --git a/packages/macros/src/with_components.rs b/packages/macros/src/with_components.rs index 2e510052a..2a0ec2816 100644 --- a/packages/macros/src/with_components.rs +++ b/packages/macros/src/with_components.rs @@ -44,8 +44,6 @@ pub fn with_components(attribute_stream: TokenStream, item_stream: TokenStream) let node = parsed.unwrap(); let (content, diagnostics) = build_patch(&db, node, components_info); - println!("\n\ncontent: {}\n\n", content); - ProcMacroResult::new(TokenStream::new(content)).with_diagnostics(diagnostics) } @@ -108,15 +106,17 @@ fn validate_contract_module( // 1. Check that the module has a body (error) let MaybeModuleBody::Some(body) = item.body(db) else { - let error = Diagnostic::error("Contract module must have a body"); + let error = Diagnostic::error(indoc! {" + Contract module must have a body. + "}); return (vec![error], vec![]); }; // 2. Check that the module has the `#[starknet::contract]` attribute (error) if !item.has_attr(db, CONTRACT_ATTRIBUTE) { - let error = Diagnostic::error( - "Contract module must have the `#[starknet::contract]` attribute", - ); + let error = Diagnostic::error(formatdoc! {" + Contract module must have the `#[{CONTRACT_ATTRIBUTE}]` attribute. + "}); return (vec![error], vec![]); } @@ -466,3 +466,23 @@ impl ComponentsGenerationData<'_> { RewriteNode::Text(entries.join("\n")) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_args() { + let attribute = "(ERC20, Ownable)"; + let result = parse_args(attribute); + assert_eq!(result, vec!["ERC20", "Ownable"]); + + let attribute = "ERC20"; + let result = parse_args(attribute); + assert_eq!(result, vec!["ERC20"]); + + let attribute = "(Ownable, ERC20, Other, Another)"; + let result = parse_args(attribute); + assert_eq!(result, vec!["Ownable", "ERC20", "Other", "Another"]); + } +} From 6814d0b0baa227b261dd6bf9952bd6f3e72a36cd Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 8 Jan 2025 14:28:42 +0100 Subject: [PATCH 6/9] feat: format output --- packages/macros/Cargo.lock | 172 ++++++++++++++++++ packages/macros/Cargo.toml | 1 + ...sts__test_with_components__with_erc20.snap | 8 +- ...components__with_erc20_no_initializer.snap | 11 +- ...s__test_with_components__with_ownable.snap | 8 +- ...mponents__with_ownable_no_initializer.snap | 11 +- ..._with_components__with_two_components.snap | 8 +- ...s__with_two_components_no_constructor.snap | 8 +- ...s__with_two_components_no_initializer.snap | 11 +- packages/macros/src/with_components.rs | 9 +- 10 files changed, 219 insertions(+), 28 deletions(-) diff --git a/packages/macros/Cargo.lock b/packages/macros/Cargo.lock index 691e9e1f5..555db2f49 100644 --- a/packages/macros/Cargo.lock +++ b/packages/macros/Cargo.lock @@ -29,6 +29,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "autocfg" version = "1.4.0" @@ -41,6 +47,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "cairo-lang-debug" version = "2.9.2" @@ -95,6 +111,26 @@ dependencies = [ "toml", ] +[[package]] +name = "cairo-lang-formatter" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d281a3c9aa365055ce6e201d5dd91534dfccfd2929a41b7397f665c80293c" +dependencies = [ + "anyhow", + "cairo-lang-diagnostics", + "cairo-lang-filesystem", + "cairo-lang-parser", + "cairo-lang-syntax", + "cairo-lang-utils", + "diffy", + "ignore", + "itertools 0.12.1", + "rust-analyzer-salsa", + "serde", + "thiserror", +] + [[package]] name = "cairo-lang-macro" version = "0.1.1" @@ -218,12 +254,46 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "diffy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e616e59155c92257e84970156f506287853355f58cd4a6eb167385722c32b790" +dependencies = [ + "nu-ansi-term", +] + [[package]] name = "either" version = "1.13.0" @@ -264,6 +334,19 @@ dependencies = [ "syn", ] +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -287,6 +370,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.7.0" @@ -382,12 +481,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -434,6 +549,7 @@ name = "openzeppelin_macros" version = "0.20.0" dependencies = [ "cairo-lang-defs", + "cairo-lang-formatter", "cairo-lang-macro", "cairo-lang-parser", "cairo-lang-syntax", @@ -443,6 +559,12 @@ dependencies = [ "regex", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -575,6 +697,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scarb-stable-hash" version = "1.0.0" @@ -783,6 +914,47 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/packages/macros/Cargo.toml b/packages/macros/Cargo.toml index 8c7cd4f99..b5c0716a6 100644 --- a/packages/macros/Cargo.toml +++ b/packages/macros/Cargo.toml @@ -11,6 +11,7 @@ cairo-lang-macro = "0.1" cairo-lang-parser = "2.9" cairo-lang-syntax = "2.9" cairo-lang-defs = "2.9" +cairo-lang-formatter = "2.9" indoc = "2.0.5" regex = "1.11.1" itertools = "0.14.0" diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap index 40ea203d5..8dfb157ef 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20.snap @@ -11,9 +11,10 @@ pub mod MyToken { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] erc20: ERC20Component::Storage, -} + } #[constructor] fn constructor(ref self: ContractState) { @@ -31,7 +32,8 @@ pub mod MyToken { enum Event { #[flat] ERC20Event: ERC20Component::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap index cb026ac50..188bfa10a 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_initializer.snap @@ -11,13 +11,13 @@ pub mod MyToken { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] erc20: ERC20Component::Storage, -} + } #[constructor] - fn constructor(ref self: ContractState) { - } + fn constructor(ref self: ContractState) {} use openzeppelin_token::erc20::ERC20Component; @@ -30,7 +30,8 @@ pub mod MyToken { enum Event { #[flat] ERC20Event: ERC20Component::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap index 2dfbe9ba7..7759eea47 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable.snap @@ -10,9 +10,10 @@ pub mod Owned { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] ownable: OwnableComponent::Storage, -} + } #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress) { @@ -30,7 +31,8 @@ pub mod Owned { enum Event { #[flat] OwnableEvent: OwnableComponent::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap index d1fe3d228..5519f8971 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_ownable_no_initializer.snap @@ -10,13 +10,13 @@ pub mod Owned { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] ownable: OwnableComponent::Storage, -} + } #[constructor] - fn constructor(ref self: ContractState) { - } + fn constructor(ref self: ContractState) {} use openzeppelin_access::ownable::OwnableComponent; @@ -29,7 +29,8 @@ pub mod Owned { enum Event { #[flat] OwnableEvent: OwnableComponent::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap index cabc6f8f3..87d97da85 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components.snap @@ -11,11 +11,12 @@ pub mod MyToken { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] erc20: ERC20Component::Storage, #[substorage(v0)] ownable: OwnableComponent::Storage, -} + } #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress) { @@ -39,7 +40,8 @@ pub mod MyToken { ERC20Event: ERC20Component::Event, #[flat] OwnableEvent: OwnableComponent::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap index 2c1ad4c20..c5d47130c 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_constructor.snap @@ -11,11 +11,12 @@ pub mod MyToken { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] erc20: ERC20Component::Storage, #[substorage(v0)] ownable: OwnableComponent::Storage, -} + } use openzeppelin_token::erc20::ERC20Component; use openzeppelin_access::ownable::OwnableComponent; @@ -33,7 +34,8 @@ pub mod MyToken { ERC20Event: ERC20Component::Event, #[flat] OwnableEvent: OwnableComponent::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap index cfec56055..c546aeffe 100644 --- a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_two_components_no_initializer.snap @@ -11,15 +11,15 @@ pub mod MyToken { use starknet::ContractAddress; #[storage] - pub struct Storage { #[substorage(v0)] + pub struct Storage { + #[substorage(v0)] erc20: ERC20Component::Storage, #[substorage(v0)] ownable: OwnableComponent::Storage, -} + } #[constructor] - fn constructor(ref self: ContractState) { - } + fn constructor(ref self: ContractState) {} use openzeppelin_token::erc20::ERC20Component; use openzeppelin_access::ownable::OwnableComponent; @@ -37,7 +37,8 @@ pub mod MyToken { ERC20Event: ERC20Component::Event, #[flat] OwnableEvent: OwnableComponent::Event, - }} + } +} Diagnostics: diff --git a/packages/macros/src/with_components.rs b/packages/macros/src/with_components.rs index 2a0ec2816..edcb24eeb 100644 --- a/packages/macros/src/with_components.rs +++ b/packages/macros/src/with_components.rs @@ -4,6 +4,7 @@ use crate::constants::{ }; use crate::utils::tabs; use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode}; +use cairo_lang_formatter::format_string; use cairo_lang_macro::{attribute_macro, Diagnostic, Diagnostics, ProcMacroResult, TokenStream}; use cairo_lang_parser::utils::SimpleParserDatabase; use cairo_lang_syntax::node::ast::MaybeModuleBody; @@ -44,7 +45,13 @@ pub fn with_components(attribute_stream: TokenStream, item_stream: TokenStream) let node = parsed.unwrap(); let (content, diagnostics) = build_patch(&db, node, components_info); - ProcMacroResult::new(TokenStream::new(content)).with_diagnostics(diagnostics) + let formatted_content = if content.len() > 0 { + format_string(&db, content) + } else { + content + }; + + ProcMacroResult::new(TokenStream::new(formatted_content)).with_diagnostics(diagnostics) } /// Parses the arguments from the attribute stream. From 44c64917e262a181d5fd1382a900b43f23a98a43 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 8 Jan 2025 15:42:10 +0100 Subject: [PATCH 7/9] feat: add warnings --- ..._with_components__with_access_control.snap | 47 ++++++++ ...s__with_access_control_no_initializer.snap | 49 ++++++++ ...s__test_with_components__with_vesting.snap | 59 ++++++++++ ...mponents__with_vesting_no_initializer.snap | 64 ++++++++++ .../macros/src/tests/test_with_components.rs | 110 ++++++++++++++++++ packages/macros/src/with_components.rs | 68 ++++++++++- 6 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control_no_initializer.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_initializer.snap diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control.snap new file mode 100644 index 000000000..cbf2d1041 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control.snap @@ -0,0 +1,47 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod Contract { + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + } + + #[constructor] + fn constructor(ref self: ContractState, default_admin: ContractAddress) { + self.accesscontrol.initializer(); + + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin); + } + + use openzeppelin_access::accesscontrol::AccessControlComponent; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + } +} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control_no_initializer.snap new file mode 100644 index 000000000..a8cf54e82 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_access_control_no_initializer.snap @@ -0,0 +1,49 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod Contract { + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + } + + #[constructor] + fn constructor(ref self: ContractState) {} + + use openzeppelin_access::accesscontrol::AccessControlComponent; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + } +} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +AccessControl + +This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting.snap new file mode 100644 index 000000000..379344a5b --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting.snap @@ -0,0 +1,59 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod VestingWallet { + use openzeppelin_finance::vesting::LinearVestingSchedule; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + vesting: VestingComponent::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } + + use openzeppelin_finance::vesting::VestingComponent; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl VestingInternalImpl = VestingComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + VestingEvent: VestingComponent::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } +} + + +Diagnostics: + +None + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_initializer.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_initializer.snap new file mode 100644 index 000000000..94105e9e4 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_initializer.snap @@ -0,0 +1,64 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod VestingWallet { + use openzeppelin_finance::vesting::LinearVestingSchedule; + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + vesting: VestingComponent::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + } + + use openzeppelin_finance::vesting::VestingComponent; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl VestingInternalImpl = VestingComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + VestingEvent: VestingComponent::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } +} + + +Diagnostics: + +==== +Warning: It looks like the initilizers for the following components are missing: + +Vesting + +This may lead to unexpected behavior. We recommend adding the corresponding initializer calls to the constructor. +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs index d96159e8d..756c9db50 100644 --- a/packages/macros/src/tests/test_with_components.rs +++ b/packages/macros/src/tests/test_with_components.rs @@ -162,6 +162,116 @@ fn test_with_two_components_no_constructor() { assert_snapshot!(result); } +#[test] +fn test_with_access_control() { + let attribute = "(AccessControl)"; + let item = indoc!( + " + #[starknet::contract] + pub mod Contract { + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, default_admin: ContractAddress) { + self.accesscontrol.initializer(); + + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_access_control_no_initializer() { + let attribute = "(AccessControl)"; + let item = indoc!( + " + #[starknet::contract] + pub mod Contract { + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_vesting() { + let attribute = "(Vesting, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod VestingWallet { + use openzeppelin_finance::vesting::LinearVestingSchedule; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_vesting_no_initializer() { + let attribute = "(Vesting, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod VestingWallet { + use openzeppelin_finance::vesting::LinearVestingSchedule; + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + #[test] fn test_with_no_contract_attribute() { let attribute = "(Ownable)"; diff --git a/packages/macros/src/with_components.rs b/packages/macros/src/with_components.rs index edcb24eeb..09e1d5287 100644 --- a/packages/macros/src/with_components.rs +++ b/packages/macros/src/with_components.rs @@ -15,7 +15,7 @@ use indoc::{formatdoc, indoc}; use itertools::Itertools; use regex::Regex; -const ALLOWED_COMPONENTS: [&str; 2] = ["ERC20", "Ownable"]; +const ALLOWED_COMPONENTS: [&str; 4] = ["ERC20", "AccessControl", "Ownable", "Vesting"]; /// Inserts multiple component dependencies into a modules codebase. #[attribute_macro] @@ -43,7 +43,13 @@ pub fn with_components(attribute_stream: TokenStream, item_stream: TokenStream) // 3. Build the patch let node = parsed.unwrap(); - let (content, diagnostics) = build_patch(&db, node, components_info); + let (content, mut diagnostics) = build_patch(&db, node, components_info.clone()); + + // 4. Add warnings for each component + for component_info in components_info.iter() { + let component_warnings = add_per_component_warnings(&content, component_info); + diagnostics.extend(component_warnings); + } let formatted_content = if content.len() > 0 { format_string(&db, content) @@ -127,7 +133,7 @@ fn validate_contract_module( return (vec![error], vec![]); } - // 3. Check that the module has the corresponding initializers (warning) + // 4. Check that the module has the corresponding initializers (warning) let components_with_initializer = components_info .iter() .filter(|c| c.has_initializer) @@ -182,6 +188,44 @@ fn validate_contract_module( (vec![], vec![]) } +/// Adds warnings that may be helpful for users. +fn add_per_component_warnings(code: &str, component_info: &ComponentInfo) -> Vec { + let mut warnings = vec![]; + + match component_info.short_name().as_str() { + "ERC20" => { + // 1. Check that the ERC20HooksTrait is implemented + let hooks_trait_used = code.contains("ERC20HooksTrait"); + let hooks_empty_impl_used = code.contains("ERC20HooksEmptyImpl"); + if !hooks_trait_used && !hooks_empty_impl_used { + let warning = Diagnostic::warn(indoc! {" + The ERC20 component requires an implementation of the ERC20HooksTrait in scope. It looks like this implementation is missing. + + You can use the ERC20HooksEmptyImpl implementation by importing it: + `use openzeppelin_token::erc20::ERC20HooksEmptyImpl;` + "}); + warnings.push(warning); + } + } + "Vesting" => { + // 1. Check that the VestingScheduleTrait is implemented + let linear_impl_used = code.contains("LinearVestingSchedule"); + let vesting_trait_used = code.contains("VestingScheduleTrait"); + if !linear_impl_used && !vesting_trait_used { + let warning = Diagnostic::warn(indoc! {" + The Vesting component requires an implementation of the VestingScheduleTrait in scope. It looks like this implementation is missing. + + You can use the LinearVestingSchedule implementation by importing it: + `use openzeppelin_finance::vesting::LinearVestingSchedule;` + "}); + warnings.push(warning); + } + } + _ => {} + } + warnings +} + /// Iterates over the items in the body node and processes them. fn process_module_items( body_rnode: &mut RewriteNode, @@ -338,6 +382,22 @@ fn get_component_info(name: &str) -> (Option, Diagnostics) { has_initializer: true, internal_impls: vec!["InternalImpl".to_string()], }), + "AccessControl" => Some(ComponentInfo { + name: format!("AccessControlComponent"), + path: format!("openzeppelin_access::accesscontrol::AccessControlComponent"), + storage: format!("accesscontrol"), + event: format!("AccessControlEvent"), + has_initializer: true, + internal_impls: vec!["InternalImpl".to_string()], + }), + "Vesting" => Some(ComponentInfo { + name: format!("VestingComponent"), + path: format!("openzeppelin_finance::vesting::VestingComponent"), + storage: format!("vesting"), + event: format!("VestingEvent"), + has_initializer: true, + internal_impls: vec!["InternalImpl".to_string()], + }), _ => None, }; if info.is_none() { @@ -476,7 +536,7 @@ impl ComponentsGenerationData<'_> { #[cfg(test)] mod tests { - use super::*; + use super::parse_args; #[test] fn test_parse_args() { From 72fdfeba3c019bedb2a2dcdd6674b9ce35c24aee Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 8 Jan 2025 15:45:54 +0100 Subject: [PATCH 8/9] feat: add tests for new warnings --- .../macros/src/tests/test_with_components.rs | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/packages/macros/src/tests/test_with_components.rs b/packages/macros/src/tests/test_with_components.rs index 756c9db50..acd8b5509 100644 --- a/packages/macros/src/tests/test_with_components.rs +++ b/packages/macros/src/tests/test_with_components.rs @@ -32,19 +32,42 @@ fn test_with_erc20_no_initializer() { let attribute = "(ERC20)"; let item = indoc!( " - #[starknet::contract] - pub mod MyToken { - use openzeppelin_token::erc20::ERC20HooksEmptyImpl; - use starknet::ContractAddress; + #[starknet::contract] + pub mod MyToken { + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + use starknet::ContractAddress; - #[storage] - pub struct Storage {} + #[storage] + pub struct Storage {} - #[constructor] - fn constructor(ref self: ContractState) { - } - } - " + #[constructor] + fn constructor(ref self: ContractState) { + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + +#[test] +fn test_with_erc20_no_hooks_impl() { + let attribute = "(ERC20)"; + let item = indoc!( + " + #[starknet::contract] + pub mod MyToken { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer(\"MyToken\", \"MTK\"); + } + } + " ); let result = get_string_result(attribute, item); assert_snapshot!(result); @@ -272,6 +295,36 @@ fn test_with_vesting_no_initializer() { assert_snapshot!(result); } +#[test] +fn test_with_vesting_no_schedule() { + let attribute = "(Vesting, Ownable)"; + let item = indoc!( + " + #[starknet::contract] + pub mod VestingWallet { + use starknet::ContractAddress; + + #[storage] + pub struct Storage {} + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } + } + " + ); + let result = get_string_result(attribute, item); + assert_snapshot!(result); +} + #[test] fn test_with_no_contract_attribute() { let attribute = "(Ownable)"; From e81beecde8f70e8e719ce6aa84bf4cdb3631b089 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 8 Jan 2025 15:47:15 +0100 Subject: [PATCH 9/9] feat: add snapshots --- ..._components__with_erc20_no_hooks_impl.snap | 49 +++++++++++++++ ..._components__with_vesting_no_schedule.snap | 63 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_hooks_impl.snap create mode 100644 packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_schedule.snap diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_hooks_impl.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_hooks_impl.snap new file mode 100644 index 000000000..236bb00f2 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_erc20_no_hooks_impl.snap @@ -0,0 +1,49 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod MyToken { + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer("MyToken", "MTK"); + } + + use openzeppelin_token::erc20::ERC20Component; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } +} + + +Diagnostics: + +==== +Warning: The ERC20 component requires an implementation of the ERC20HooksTrait in scope. It looks like this implementation is missing. + +You can use the ERC20HooksEmptyImpl implementation by importing it: +`use openzeppelin_token::erc20::ERC20HooksEmptyImpl;` +==== + +AuxData: + +None diff --git a/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_schedule.snap b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_schedule.snap new file mode 100644 index 000000000..b7c759070 --- /dev/null +++ b/packages/macros/src/tests/snapshots/openzeppelin_macros__tests__test_with_components__with_vesting_no_schedule.snap @@ -0,0 +1,63 @@ +--- +source: src/tests/test_with_components.rs +expression: result +snapshot_kind: text +--- +TokenStream: + +#[starknet::contract] +pub mod VestingWallet { + use starknet::ContractAddress; + + #[storage] + pub struct Storage { + #[substorage(v0)] + vesting: VestingComponent::Storage, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + } + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64, + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } + + use openzeppelin_finance::vesting::VestingComponent; + use openzeppelin_access::ownable::OwnableComponent; + + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + impl VestingInternalImpl = VestingComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + VestingEvent: VestingComponent::Event, + #[flat] + OwnableEvent: OwnableComponent::Event, + } +} + + +Diagnostics: + +==== +Warning: The Vesting component requires an implementation of the VestingScheduleTrait in scope. It looks like this implementation is missing. + +You can use the LinearVestingSchedule implementation by importing it: +`use openzeppelin_finance::vesting::LinearVestingSchedule;` +==== + +AuxData: + +None