diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index d36718ef9..797ad4df4 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -113,6 +113,54 @@ jobs: - name: cargo clippy --workspace --all-targets -- -D warnings run: cargo clippy --workspace --all-targets -- -D warnings + cargo-check-lints: + name: check custom lints + runs-on: SubtensorCI + strategy: + matrix: + rust-branch: + - stable + rust-target: + - x86_64-unknown-linux-gnu + # - x86_64-apple-darwin + os: + - ubuntu-latest + # - macos-latest + env: + RELEASE_NAME: development + RUSTV: ${{ matrix.rust-branch }} + RUSTFLAGS: -D warnings + RUST_BACKTRACE: full + RUST_BIN_DIR: target/${{ matrix.rust-target }} + SKIP_WASM_BUILD: 1 + TARGET: ${{ matrix.rust-target }} + steps: + - name: Check-out repository under $GITHUB_WORKSPACE + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update && + sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler + + - name: Install Rust ${{ matrix.rust-branch }} + uses: actions-rs/toolchain@v1.0.6 + with: + toolchain: ${{ matrix.rust-branch }} + components: rustfmt, clippy + profile: minimal + + - name: Utilize Shared Rust Cache + uses: Swatinem/rust-cache@v2.2.1 + with: + key: ${{ matrix.os }}-${{ env.RUST_BIN_DIR }} + + - name: check lints + run: | + set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails + cargo check 2>&1 | sed -r "s/\x1B\[[0-9;]*[mK]//g" | tee /dev/tty | grep -q "^warning:" && \ + (echo "Build emitted the following warnings:" >&2 && exit 1) || echo "No warnings found." + cargo-clippy-all-features: name: cargo clippy --all-features runs-on: SubtensorCI diff --git a/Cargo.lock b/Cargo.lock index add1b713d..350831723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,7 +223,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -625,7 +625,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -751,7 +751,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1159,7 +1159,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1553,7 +1553,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1580,7 +1580,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1597,7 +1597,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1621,7 +1621,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1632,7 +1632,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1738,7 +1738,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1751,7 +1751,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1840,7 +1840,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1880,7 +1880,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.67", + "syn 2.0.71", "termcolor", "toml 0.8.14", "walkdir", @@ -2040,7 +2040,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2153,7 +2153,7 @@ dependencies = [ "prettyplease 0.2.20", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2504,7 +2504,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2516,7 +2516,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2526,7 +2526,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2 dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2677,7 +2677,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -3468,7 +3468,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4219,7 +4219,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4233,7 +4233,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4244,7 +4244,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4255,7 +4255,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5622,7 +5622,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5663,7 +5663,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5749,7 +5749,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5759,7 +5759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5886,7 +5886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5954,7 +5954,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6000,7 +6000,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6068,7 +6068,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6336,7 +6336,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6795,7 +6795,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7629,7 +7629,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7939,7 +7939,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7987,7 +7987,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8220,7 +8220,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8443,7 +8443,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2 dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8462,7 +8462,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2 dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8472,7 +8472,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8697,7 +8697,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8710,7 +8710,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8923,7 +8923,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9092,7 +9092,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9182,6 +9182,23 @@ dependencies = [ "wasm-opt", ] +[[package]] +name = "subtensor" +version = "0.1.0" +dependencies = [ + "node-subtensor", + "node-subtensor-runtime", + "pallet-commitments", + "pallet-subtensor", + "proc-macro2", + "quote", + "rayon", + "subtensor-linting", + "subtensor-macros", + "syn 2.0.71", + "walkdir", +] + [[package]] name = "subtensor-custom-rpc" version = "0.0.2" @@ -9207,6 +9224,15 @@ dependencies = [ "sp-api", ] +[[package]] +name = "subtensor-linting" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "subtensor-macros" version = "0.1.0" @@ -9214,7 +9240,7 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9242,9 +9268,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -9350,7 +9376,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9470,7 +9496,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9628,7 +9654,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -10033,7 +10059,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -10067,7 +10093,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10786,7 +10812,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -10806,7 +10832,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4a7565a01..36a87755b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,40 @@ +[package] +name = "subtensor" +version = "0.1.0" +description = "Implementation of the bittensor blockchain" +authors = ["Substrate DevHub "] +homepage = "https://substrate.io/" +edition = "2021" +license = "Unlicense" +publish = false +repository = "https://github.com/opentensor/subtensor" + +[dependencies] +node-subtensor = { path = "node", version = "4.0.0-dev" } +pallet-commitments = { path = "pallets/commitments", version = "4.0.0-dev" } +pallet-subtensor = { path = "pallets/subtensor", version = "4.0.0-dev" } +node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } +subtensor-macros = { path = "support/macros", version = "0.1.0" } + +[build-dependencies] +subtensor-linting = { path = "support/linting", version = "0.1.0" } +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true +walkdir.workspace = true +rayon = "1.10" + [workspace] members = [ "node", "pallets/commitments", "pallets/subtensor", + "pallets/admin-utils", + "pallets/collective", + "pallets/registry", "runtime", "support/macros", + "support/linting", ] resolver = "2" @@ -36,6 +66,10 @@ serde_json = { version = "1.0.116", default-features = false } serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } +syn = { version = "2", features = ["full", "visit-mut", "visit", "extra-traits", "parsing"] } +quote = "1" +proc-macro2 = { version = "1", features = ["span-locations"] } +walkdir = "2" subtensor-macros = { path = "support/macros" } diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..10cac0ea7 --- /dev/null +++ b/build.rs @@ -0,0 +1,96 @@ +use rayon::prelude::*; +use std::{ + env, fs, + path::{Path, PathBuf}, + str::FromStr, + sync::mpsc::channel, +}; +use walkdir::WalkDir; + +use subtensor_linting::*; + +fn main() { + // need to list all rust directories here + println!("cargo:rerun-if-changed=pallets"); + println!("cargo:rerun-if-changed=node"); + println!("cargo:rerun-if-changed=runtime"); + println!("cargo:rerun-if-changed=lints"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src"); + println!("cargo:rerun-if-changed=support"); + // Get the root directory of the workspace + let workspace_root = env::var("CARGO_MANIFEST_DIR").unwrap(); + let workspace_root = Path::new(&workspace_root); + + // Collect all Rust source files in the workspace + let rust_files = collect_rust_files(workspace_root); + + // Channel used to communicate errors back to the main thread from the parallel processing + // as we process each Rust file + let (tx, rx) = channel(); + + // Parse each rust file with syn and run the linting suite on it in parallel + rust_files.par_iter().for_each_with(tx.clone(), |tx, file| { + let Ok(content) = fs::read_to_string(file) else { + return; + }; + let Ok(parsed_tokens) = proc_macro2::TokenStream::from_str(&content) else { + return; + }; + let Ok(parsed_file) = syn::parse2::(parsed_tokens) else { + return; + }; + + let track_lint = |result: Result| { + let Err(errors) = result else { + return; + }; + let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); + for error in errors { + let loc = error.span().start(); + let file_path = relative_path.display(); + // note that spans can't go across thread boundaries without losing their location + // info so we we serialize here and send a String + tx.send(format!( + "cargo:warning={}:{}:{}: {}", + file_path, loc.line, loc.column, error, + )) + .unwrap(); + } + }; + + track_lint(RequireFreezeStruct::lint(&parsed_file)); + }); + + // Collect and print all errors after the parallel processing is done + drop(tx); // Close the sending end of the channel + + for error in rx { + println!("{error}"); + } +} + +/// Recursively collects all Rust files in the given directory +fn collect_rust_files(dir: &Path) -> Vec { + let mut rust_files = Vec::new(); + + for entry in WalkDir::new(dir) { + let entry = entry.unwrap(); + let path = entry.path(); + + // Skip any path that contains "target" directory + if path + .components() + .any(|component| component.as_os_str() == "target") + || path.ends_with("build.rs") + { + continue; + } + + if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + rust_files.push(path.to_path_buf()); + } + } + + rust_files +} diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 6aae3c85e..823e92663 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -165,6 +165,7 @@ pub struct Votes { /// The hard end time of this vote. end: BlockNumber, } + #[deny(missing_docs)] #[frame_support::pallet] pub mod pallet { diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 912a474c0..06bafcaac 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -299,6 +299,7 @@ pub struct CommitmentInfo> { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. +#[freeze_struct("632f12850e51c420")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 0badd5669..58cc5ed19 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -367,6 +367,7 @@ impl> IdentityInfo { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. +#[freeze_struct("797b69e82710bb21")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b305661f7..466ecf966 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -59,7 +59,6 @@ extern crate alloc; #[import_section(config::config)] #[frame_support::pallet] pub mod pallet { - use crate::migrations; use frame_support::{ dispatch::GetDispatchInfo, @@ -96,6 +95,7 @@ pub mod pallet { pub type AxonInfoOf = AxonInfo; /// Data structure for Axon information. + #[crate::freeze_struct("3545cfb0cac4c1f5")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct AxonInfo { /// Axon serving block. @@ -118,7 +118,9 @@ pub mod pallet { /// Struct for Prometheus. pub type PrometheusInfoOf = PrometheusInfo; + /// Data structure for Prometheus information. + #[crate::freeze_struct("5dde687e63baf0cd")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct PrometheusInfo { /// Prometheus serving block. @@ -135,7 +137,9 @@ pub mod pallet { /// Struct for Prometheus. pub type ChainIdentityOf = ChainIdentity; + /// Data structure for Prometheus information. + #[crate::freeze_struct("bbfd00438dbe2b58")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct ChainIdentity { /// The name of the chain identity diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d047288dd..507747e8c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -141,7 +141,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 192, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/scripts/localnet.sh b/scripts/localnet.sh index 2856603e0..850a314d8 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -48,7 +48,7 @@ fi if [[ $BUILD_BINARY == "1" ]]; then echo "*** Building substrate binary..." - cargo build --release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" + cargo build --workspace --profile=release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" echo "*** Binary compiled" fi diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ + diff --git a/support/linting/Cargo.toml b/support/linting/Cargo.toml new file mode 100644 index 000000000..1e37d8163 --- /dev/null +++ b/support/linting/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "subtensor-linting" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true + +[lints] +workspace = true diff --git a/support/linting/src/lib.rs b/support/linting/src/lib.rs new file mode 100644 index 000000000..d02a70a2b --- /dev/null +++ b/support/linting/src/lib.rs @@ -0,0 +1,6 @@ +pub mod lint; +pub use lint::*; + +mod require_freeze_struct; + +pub use require_freeze_struct::RequireFreezeStruct; diff --git a/support/linting/src/lint.rs b/support/linting/src/lint.rs new file mode 100644 index 000000000..3c099d40c --- /dev/null +++ b/support/linting/src/lint.rs @@ -0,0 +1,13 @@ +use syn::File; + +pub type Result = core::result::Result<(), Vec>; + +/// A trait that defines custom lints that can be run within our workspace. +/// +/// Each lint is run in parallel on all Rust source files in the workspace. Within a lint you +/// can issue an error the same way you would in a proc macro, and otherwise return `Ok(())` if +/// there are no errors. +pub trait Lint: Send + Sync { + /// Lints the given Rust source file, returning a compile error if any issues are found. + fn lint(source: &File) -> Result; +} diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs new file mode 100644 index 000000000..8f02e2697 --- /dev/null +++ b/support/linting/src/require_freeze_struct.rs @@ -0,0 +1,184 @@ +use super::*; +use syn::{ + parse_quote, punctuated::Punctuated, visit::Visit, Attribute, File, ItemStruct, Meta, MetaList, + Path, Token, +}; + +pub struct RequireFreezeStruct; + +impl Lint for RequireFreezeStruct { + fn lint(source: &File) -> Result { + let mut visitor = EncodeDecodeVisitor::default(); + + visitor.visit_file(source); + + if !visitor.errors.is_empty() { + return Err(visitor.errors); + } + + Ok(()) + } +} + +#[derive(Default)] +struct EncodeDecodeVisitor { + errors: Vec, +} + +impl<'ast> Visit<'ast> for EncodeDecodeVisitor { + fn visit_item_struct(&mut self, node: &'ast ItemStruct) { + let has_encode_decode = node.attrs.iter().any(is_derive_encode_or_decode); + let has_freeze_struct = node.attrs.iter().any(is_freeze_struct); + + if has_encode_decode && !has_freeze_struct { + self.errors.push(syn::Error::new( + node.ident.span(), + "Struct with Encode/Decode derive must also have #[freeze_struct(..)] attribute.", + )); + } + + syn::visit::visit_item_struct(self, node); + } +} + +fn is_freeze_struct(attr: &Attribute) -> bool { + if let Meta::List(meta_list) = &attr.meta { + let Some(seg) = meta_list.path.segments.last() else { + return false; + }; + if seg.ident == "freeze_struct" && !meta_list.tokens.is_empty() { + return true; + } + } + false +} + +fn is_derive_encode_or_decode(attr: &Attribute) -> bool { + if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { + if path.is_ident("derive") { + let nested: Punctuated = parse_quote!(#tokens); + return nested.iter().any(|nested| { + nested.segments.iter().any(|seg| seg.ident == "Encode") + || nested.segments.iter().any(|seg| seg.ident == "Decode") + }); + } + } + false +} + +#[cfg(test)] +mod tests { + use super::*; + + fn lint_struct(input: &str) -> Result { + let item_struct: ItemStruct = syn::parse_str(input).expect("should only use on a struct"); + let mut visitor = EncodeDecodeVisitor::default(); + visitor.visit_item_struct(&item_struct); + if !visitor.errors.is_empty() { + return Err(visitor.errors); + } + Ok(()) + } + + #[test] + fn test_no_attributes() { + let input = r#" + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_freeze_struct_only() { + let input = r#" + #[freeze_struct("12345")] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_encode_only() { + let input = r#" + #[derive(Encode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_decode_only() { + let input = r#" + #[derive(Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_encode_and_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Encode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_decode_and_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_encode_decode_without_freeze_struct() { + let input = r#" + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_encode_decode_with_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_temporary_freeze_struct() { + let input = r#" + #[freeze_struct] + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } +} diff --git a/support/macros/Cargo.toml b/support/macros/Cargo.toml index 10a15ba0d..b5a5febad 100644 --- a/support/macros/Cargo.toml +++ b/support/macros/Cargo.toml @@ -12,9 +12,9 @@ homepage = "https://bittensor.com/" proc-macro = true [dependencies] -syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } -proc-macro2 = "1" -quote = "1" +syn.workspace = true +proc-macro2.workspace = true +quote.workspace = true ahash = "0.8" [lints]