From 55df991f39a0c150e13bf406d344384310fbb5d7 Mon Sep 17 00:00:00 2001 From: KeNgKawthar Date: Sun, 26 Jul 2026 12:12:36 +0100 Subject: [PATCH] (Detect-Unpacked-Storage-Slots-in-Vyper-Structs Detect-Unpacked-Storage-Slots-in-Vyper-Structs --- Cargo.lock | 1299 ++++++++++++++++- packages/rules/src/lib.rs | 7 +- packages/rules/src/vyper/mod.rs | 2 + packages/rules/src/vyper/parser.rs | 232 ++- .../rules/src/vyper/redundant_external.rs | 1 + packages/rules/src/vyper/storage_packing.rs | 413 ++++++ 6 files changed, 1894 insertions(+), 60 deletions(-) create mode 100644 packages/rules/src/vyper/storage_packing.rs diff --git a/Cargo.lock b/Cargo.lock index ec19eef..29f5ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "access-control-example" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -86,6 +108,15 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -94,7 +125,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -103,11 +134,56 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" -version = "2.10.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -118,11 +194,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -130,11 +224,23 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "bytes-lit" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "cc" -version = "1.2.54" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "shlex", @@ -191,7 +297,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -216,6 +322,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -231,6 +343,38 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -241,6 +385,166 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "diff" version = "0.1.13" @@ -253,8 +557,20 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", ] [[package]] @@ -263,6 +579,80 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -279,11 +669,51 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + +[[package]] +name = "ethnum" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fragile" @@ -305,7 +735,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -460,7 +890,7 @@ dependencies = [ "rstest", "serde", "serde_json", - "syn", + "syn 2.0.114", "thiserror", ] @@ -472,14 +902,51 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.16.1" @@ -497,6 +964,33 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] [[package]] name = "iana-time-zone" @@ -522,6 +1016,23 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -529,15 +1040,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -546,14 +1074,36 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -562,9 +1112,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -576,6 +1126,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "lock_api" version = "0.4.14" @@ -597,6 +1153,15 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.1.1" @@ -631,7 +1196,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -641,6 +1206,42 @@ dependencies = [ "gasguard-rules", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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" @@ -650,6 +1251,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -662,6 +1272,18 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -685,6 +1307,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -697,6 +1325,31 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "predicates" version = "3.1.3" @@ -723,6 +1376,25 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -742,21 +1414,71 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.44" +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ - "proc-macro2", + "ref-cast-impl", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "ref-cast-impl" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ - "bitflags", + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] @@ -794,6 +1516,16 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rstest" version = "0.26.1" @@ -819,10 +1551,16 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.114", "unicode-ident", ] +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc_version" version = "0.4.1" @@ -847,12 +1585,49 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.27" @@ -886,7 +1661,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -902,6 +1677,38 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "sha2" version = "0.10.9" @@ -909,8 +1716,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -926,11 +1733,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -942,6 +1759,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + [[package]] name = "slab" version = "0.4.11" @@ -964,12 +1791,253 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "soroban-env-common" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", + "wasmparser", +] + +[[package]] +name = "soroban-env-guest" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" +dependencies = [ + "backtrace", + "curve25519-dalek 5.0.0", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", + "static_assertions", + "stellar-strkey", + "wasmparser", +] + +[[package]] +name = "soroban-env-macros" +version = "21.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.114", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6edf92749fd8399b417192d301c11f710b9cdce15789a3d157785ea971576fa" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common", + "soroban-env-host", + "thiserror", +] + +[[package]] +name = "soroban-sdk" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcdf04484af7cc731a7a48ad1d9f5f940370edeea84734434ceaf398a6b862e" +dependencies = [ + "arbitrary", + "bytes-lit", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey", +] + +[[package]] +name = "soroban-sdk-macros" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0974e413731aeff2443f2305b344578b3f1ffd18335a7ba0f0b5d2eb4e94c9ce" +dependencies = [ + "crate-git-revision", + "darling 0.20.11", + "itertools", + "proc-macro2", + "quote", + "rustc_version", + "sha2", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn 2.0.114", +] + +[[package]] +name = "soroban-spec" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c70b20e68cae3ef700b8fa3ae29db1c6a294b311fba66918f90cb8f9fd0a1a" +dependencies = [ + "base64 0.13.1", + "stellar-xdr", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "21.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2dafbde981b141b191c6c036abc86097070ddd6eaaa33b273701449501e43d3" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr", + "syn 2.0.114", + "thiserror", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-strkey" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" +dependencies = [ + "base32", + "crate-git-revision", + "thiserror", +] + +[[package]] +name = "stellar-xdr" +version = "21.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" +dependencies = [ + "arbitrary", + "base64 0.13.1", + "crate-git-revision", + "escape-bytes", + "hex", + "serde", + "serde_with", + "stellar-strkey", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.114" @@ -981,6 +2049,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termtree" version = "0.5.1" @@ -1004,7 +2083,59 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "token-example" +version = "0.1.0" +dependencies = [ + "soroban-sdk", ] [[package]] @@ -1032,7 +2163,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1050,7 +2181,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -1077,6 +2208,13 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "upgradeable-example" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -1107,9 +2245,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1120,9 +2258,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1130,26 +2268,63 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -1180,7 +2355,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1191,7 +2366,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1383,6 +2558,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zmij" version = "1.0.17" diff --git a/packages/rules/src/lib.rs b/packages/rules/src/lib.rs index 8098047..0a50ca7 100644 --- a/packages/rules/src/lib.rs +++ b/packages/rules/src/lib.rs @@ -19,11 +19,8 @@ pub use optimization::storage::{ pub use rule_engine::{ extract_struct_fields, find_variable_usage, Rule, RuleEngine, RuleViolation, ViolationSeverity, }; -pub use security::{HardcodedAddressesRule, MissingDomainSeparationRule}; -pub use solidity::{DynamicArrayDeletionRule, MappingIterationRule, StateVariablePackingRule}; -pub use security::{HardcodedAddressesRule, MissingDomainSeparationRule, defi::MissingSlippageValidationRule}; -pub use solidity::{StateVariablePackingRule, MappingIterationRule, AbiEncodingRule}; -pub use optimization::storage::detect_mapping_iteration; +pub use security::{defi::MissingSlippageValidationRule, HardcodedAddressesRule, MissingDomainSeparationRule}; +pub use solidity::{AbiEncodingRule, DynamicArrayDeletionRule, MappingIterationRule, StateVariablePackingRule}; pub use unused_state_variables::UnusedStateVariablesRule; // Export Soroban types specifically diff --git a/packages/rules/src/vyper/mod.rs b/packages/rules/src/vyper/mod.rs index 930d630..dab0592 100644 --- a/packages/rules/src/vyper/mod.rs +++ b/packages/rules/src/vyper/mod.rs @@ -1,5 +1,7 @@ pub mod parser; pub mod redundant_external; +pub mod storage_packing; pub use parser::*; pub use redundant_external::*; +pub use storage_packing::*; diff --git a/packages/rules/src/vyper/parser.rs b/packages/rules/src/vyper/parser.rs index 69bc1df..0158281 100644 --- a/packages/rules/src/vyper/parser.rs +++ b/packages/rules/src/vyper/parser.rs @@ -18,11 +18,29 @@ pub struct VyperFunctionCall { pub line_number: usize, } +/// Represents a field within a Vyper struct +#[derive(Debug, Clone)] +pub struct VyperStructField { + pub name: String, + pub type_name: String, + pub size_bytes: usize, + pub line_number: usize, +} + +/// Represents a Vyper struct definition +#[derive(Debug, Clone)] +pub struct VyperStruct { + pub name: String, + pub fields: Vec, + pub line_number: usize, +} + /// Parsed Vyper contract representation #[derive(Debug, Clone)] pub struct VyperContract { pub functions: Vec, pub function_calls: Vec, + pub structs: Vec, } impl VyperContract { @@ -30,6 +48,7 @@ impl VyperContract { pub fn parse(source: &str) -> Result { let mut functions = Vec::new(); let mut function_calls = Vec::new(); + let mut structs = Vec::new(); let mut current_decorators: Vec = Vec::new(); let mut decorator_start_line: Option = None; @@ -37,6 +56,10 @@ impl VyperContract { let decorator_pattern = Regex::new(r"^@(\w+)").map_err(|e| e.to_string())?; let function_pattern = Regex::new(r"^def\s+(\w+)\s*\(").map_err(|e| e.to_string())?; let self_call_pattern = Regex::new(r"self\.(\w+)\s*\(").map_err(|e| e.to_string())?; + let struct_pattern = Regex::new(r"^struct\s+(\w+)").map_err(|e| e.to_string())?; + let field_pattern = Regex::new(r"^(\w+)\s*:\s*(\w+)").map_err(|e| e.to_string())?; + + let mut current_struct: Option = None; for (line_idx, line) in source.lines().enumerate() { let line_number = line_idx + 1; @@ -51,6 +74,70 @@ impl VyperContract { current_decorators.push(decorator_name.as_str().to_string()); } } + // Check for struct definition + else if let Some(captures) = struct_pattern.captures(trimmed) { + // Save any pending struct + if let Some(struct_def) = current_struct.take() { + if !struct_def.fields.is_empty() { + structs.push(struct_def); + } + } + + let struct_name = captures.get(1).unwrap().as_str().to_string(); + current_struct = Some(VyperStruct { + name: struct_name, + fields: Vec::new(), + line_number, + }); + } + // Check for struct field (only if inside a struct) + else if current_struct.is_some() { + if trimmed.starts_with("#") || trimmed.is_empty() { + continue; + } + + // Check if struct ended + if trimmed.starts_with("def ") + || trimmed.starts_with("@") + || trimmed.starts_with("event ") + || trimmed.starts_with("interface ") + || trimmed.starts_with("struct ") + { + if let Some(struct_def) = current_struct.take() { + if !struct_def.fields.is_empty() { + structs.push(struct_def); + } + } + + // Process the current line for functions/decorators + if let Some(captures) = function_pattern.captures(trimmed) { + if let Some(func_name) = captures.get(1) { + let func_line = decorator_start_line.unwrap_or(line_number); + functions.push(VyperFunction { + name: func_name.as_str().to_string(), + decorators: current_decorators.clone(), + line_number: func_line, + column_number: 1, + }); + current_decorators.clear(); + decorator_start_line = None; + } + } + } else if let Some(captures) = field_pattern.captures(trimmed) { + let field_name = captures.get(1).unwrap().as_str().to_string(); + let type_name = captures.get(2).unwrap().as_str().to_string(); + let size_bytes = get_vyper_type_size(&type_name); + + if let Some(ref mut struct_def) = current_struct { + struct_def.fields.push(VyperStructField { + name: field_name, + type_name, + size_bytes, + line_number, + }); + } + } + } // Check for function definition else if let Some(captures) = function_pattern.captures(trimmed) { if let Some(func_name) = captures.get(1) { @@ -65,12 +152,6 @@ impl VyperContract { decorator_start_line = None; } } - // Check for non-decorator, non-function lines (reset decorators if we hit something else) - else if !trimmed.is_empty() && !trimmed.starts_with('#') { - // If we encounter a non-empty, non-comment line that's not a decorator or function, - // and we have pending decorators, they might be orphaned (edge case) - // For now, we keep collecting decorators until we hit a function - } // Track self.function() calls for internal usage analysis for captures in self_call_pattern.captures_iter(line) { @@ -84,9 +165,17 @@ impl VyperContract { } } + // Don't forget the last struct + if let Some(struct_def) = current_struct.take() { + if !struct_def.fields.is_empty() { + structs.push(struct_def); + } + } + Ok(VyperContract { functions, function_calls, + structs, }) } @@ -110,6 +199,71 @@ impl VyperContract { } } +/// Get the size of a Vyper type in bytes +pub fn get_vyper_type_size(type_name: &str) -> usize { + let base_type = type_name.trim().to_lowercase(); + + // Handle decimal types + if base_type.starts_with("decimal") { + return 32; + } + + // Handle unsigned integers + if base_type.starts_with("uint") { + if let Some(bits_str) = base_type.strip_prefix("uint") { + if bits_str.is_empty() { + return 32; // uint = uint256 + } + if let Ok(bits) = bits_str.parse::() { + return bits / 8; + } + } + } + + // Handle signed integers + if base_type.starts_with("int") { + if let Some(bits_str) = base_type.strip_prefix("int") { + if bits_str.is_empty() { + return 32; // int = int256 + } + if let Ok(bits) = bits_str.parse::() { + return bits / 8; + } + } + } + + // Handle bytes types + if base_type.starts_with("bytes") { + if let Some(size_str) = base_type.strip_prefix("bytes") { + if size_str.is_empty() { + return 32; // bytes = bytes32 (dynamic in some contexts but typically 32) + } + if let Ok(size) = size_str.parse::() { + return size; + } + } + } + + // Handle string types + if base_type == "string" { + return 32; // Dynamic type, treated as 32 bytes for slot calculations + } + + // Handle special types + match base_type.as_str() { + "bool" => 1, + "address" => 20, + "byte" => 1, + _ => { + // Handle dynamic arrays and other complex types + if base_type.contains("[]") || base_type.contains("[") { + return 32; // Dynamic types occupy a full slot + } + 32 // Default fallback + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -186,4 +340,70 @@ def _helper(): )); assert!(!VyperContract::is_internal_naming_convention("__init__")); // Dunder methods excluded } + + #[test] + fn test_parse_struct() { + let source = r#" +struct User: + is_active: bool + balance: uint256 + nonce: uint8 +"#; + let contract = VyperContract::parse(source).unwrap(); + assert_eq!(contract.structs.len(), 1); + assert_eq!(contract.structs[0].name, "User"); + assert_eq!(contract.structs[0].fields.len(), 3); + assert_eq!(contract.structs[0].fields[0].name, "is_active"); + assert_eq!(contract.structs[0].fields[0].type_name, "bool"); + assert_eq!(contract.structs[0].fields[1].name, "balance"); + assert_eq!(contract.structs[0].fields[1].type_name, "uint256"); + assert_eq!(contract.structs[0].fields[2].name, "nonce"); + assert_eq!(contract.structs[0].fields[2].type_name, "uint8"); + } + + #[test] + fn test_parse_multiple_structs() { + let source = r#" +struct User: + active: bool + balance: uint256 + +struct Config: + enabled: bool + value: uint128 +"#; + let contract = VyperContract::parse(source).unwrap(); + assert_eq!(contract.structs.len(), 2); + assert_eq!(contract.structs[0].name, "User"); + assert_eq!(contract.structs[1].name, "Config"); + } + + #[test] + fn test_parse_struct_with_function() { + let source = r#" +struct User: + active: bool + balance: uint256 + +@external +def get_user() -> User: + pass +"#; + let contract = VyperContract::parse(source).unwrap(); + assert_eq!(contract.structs.len(), 1); + assert_eq!(contract.functions.len(), 1); + } + + #[test] + fn test_get_vyper_type_size() { + assert_eq!(get_vyper_type_size("uint8"), 1); + assert_eq!(get_vyper_type_size("uint16"), 2); + assert_eq!(get_vyper_type_size("uint256"), 32); + assert_eq!(get_vyper_type_size("uint"), 32); + assert_eq!(get_vyper_type_size("bool"), 1); + assert_eq!(get_vyper_type_size("address"), 20); + assert_eq!(get_vyper_type_size("bytes1"), 1); + assert_eq!(get_vyper_type_size("bytes32"), 32); + } } + diff --git a/packages/rules/src/vyper/redundant_external.rs b/packages/rules/src/vyper/redundant_external.rs index 4cbc244..96bd3bd 100644 --- a/packages/rules/src/vyper/redundant_external.rs +++ b/packages/rules/src/vyper/redundant_external.rs @@ -166,6 +166,7 @@ impl VyperRuleEngine { pub fn with_default_rules() -> Self { let mut engine = Self::new(); engine.add_rule(Box::new(RedundantExternalDecoratorRule)); + engine.add_rule(Box::new(crate::vyper::storage_packing::StructStoragePackingRule)); engine } diff --git a/packages/rules/src/vyper/storage_packing.rs b/packages/rules/src/vyper/storage_packing.rs new file mode 100644 index 0000000..f629c84 --- /dev/null +++ b/packages/rules/src/vyper/storage_packing.rs @@ -0,0 +1,413 @@ +use crate::rule_engine::{RuleViolation, ViolationSeverity}; +use crate::vyper::VyperRule; +use crate::vyper::parser::VyperContract; +use serde::{Deserialize, Serialize}; + +/// Represents a struct field with its type and size information +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StructField { + pub name: String, + pub type_name: String, + pub size_bytes: usize, + pub line_number: usize, +} + +/// Represents a storage packing opportunity in a struct +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StructPackingOpportunity { + pub struct_name: String, + pub fields: Vec, + pub current_slots: usize, + pub optimal_slots: usize, + pub saved_slots: usize, + pub reordered_fields: Vec, + pub suggestion: String, +} + +/// Get the size of a Vyper type in bytes +pub fn get_vyper_type_size(type_name: &str) -> usize { + let base_type = type_name.trim().to_lowercase(); + + if base_type.starts_with("uint") { + if let Some(bits_str) = base_type.strip_prefix("uint") { + if bits_str.is_empty() { + return 32; + } + if let Ok(bits) = bits_str.parse::() { + return bits / 8; + } + } + } + + if base_type.starts_with("int") { + if let Some(bits_str) = base_type.strip_prefix("int") { + if bits_str.is_empty() { + return 32; + } + if let Ok(bits) = bits_str.parse::() { + return bits / 8; + } + } + } + + match base_type.as_str() { + "bool" => 1, + "address" => 20, + "bytes1" | "byte" => 1, + "bytes2" => 2, + "bytes3" => 3, + "bytes4" => 4, + "bytes5" => 5, + "bytes6" => 6, + "bytes7" => 7, + "bytes8" => 8, + "bytes9" => 9, + "bytes10" => 10, + "bytes11" => 11, + "bytes12" => 12, + "bytes13" => 13, + "bytes14" => 14, + "bytes15" => 15, + "bytes16" => 16, + "bytes17" => 17, + "bytes18" => 18, + "bytes19" => 19, + "bytes20" => 20, + "bytes21" => 21, + "bytes22" => 22, + "bytes23" => 23, + "bytes24" => 24, + "bytes25" => 25, + "bytes26" => 26, + "bytes27" => 27, + "bytes28" => 28, + "bytes29" => 29, + "bytes30" => 30, + "bytes31" => 31, + "bytes32" => 32, + "string" => 32, + "decimal" => 32, + _ => { + if base_type.contains("[]") || base_type.contains("[") { + return 32; + } + 32 + } + } +} + +/// Check if a Vyper type can be packed (sub-256-bit) +pub fn is_vyper_packable_type(type_name: &str) -> bool { + let base_type = type_name.trim().to_lowercase(); + + if base_type.contains("[]") + || base_type.contains("[") + || base_type == "string" + || base_type == "decimal" + { + return false; + } + + let size = get_vyper_type_size(type_name); + size < 32 +} + +/// Calculate storage slots for a given field ordering +pub fn calculate_storage_slots(fields: &[crate::vyper::parser::VyperStructField]) -> usize { + let mut slot_size = 0u32; + let mut slots = 1usize; + + for field in fields { + let size = get_vyper_type_size(&field.type_name); + if size >= 32 { + slots += 1; + slot_size = 0; + } else { + slot_size += size as u32; + if slot_size > 32 { + slots += 1; + slot_size = size as u32; + } + } + } + + slots +} + +/// Find the optimal ordering of fields to minimize storage slots +pub fn find_optimal_ordering( + fields: &[crate::vyper::parser::VyperStructField], +) -> Vec { + if fields.is_empty() { + return Vec::new(); + } + + let mut packable: Vec = fields + .iter() + .filter(|f| is_vyper_packable_type(&f.type_name)) + .cloned() + .collect(); + let mut non_packable: Vec = fields + .iter() + .filter(|f| !is_vyper_packable_type(&f.type_name)) + .cloned() + .collect(); + + packable.sort_by(|a, b| { + let size_a = get_vyper_type_size(&a.type_name); + let size_b = get_vyper_type_size(&b.type_name); + size_b.cmp(&size_a) + }); + + let mut slots: Vec> = Vec::new(); + + for field in packable { + let size = get_vyper_type_size(&field.type_name); + let mut placed = false; + for slot in &mut slots { + let current_size: usize = + slot.iter().map(|f| get_vyper_type_size(&f.type_name)).sum(); + if current_size + size <= 32 { + slot.push(field.clone()); + placed = true; + break; + } + } + if !placed { + slots.push(vec![field.clone()]); + } + } + + let mut optimal: Vec = Vec::new(); + for slot in &slots { + optimal.extend(slot.clone()); + } + + optimal.extend(non_packable); + + optimal +} + +/// Generate a suggestion string for reordering struct fields +pub fn generate_suggestion( + struct_name: &str, + original_fields: &[crate::vyper::parser::VyperStructField], + reordered_fields: &[crate::vyper::parser::VyperStructField], + saved_slots: usize, +) -> String { + let mut suggestion = format!( + "Reorder struct '{}' fields to save {} storage slot(s).\n", + struct_name, saved_slots + ); + suggestion.push_str("Current ordering:\n"); + for field in original_fields { + suggestion.push_str(&format!(" {}: {}\n", field.name, field.type_name)); + } + suggestion.push_str("Suggested ordering:\n"); + for field in reordered_fields { + suggestion.push_str(&format!(" {}: {}\n", field.name, field.type_name)); + } + suggestion +} + +/// Rule for detecting unpacked storage slots in Vyper structs +pub struct StructStoragePackingRule; + +impl VyperRule for StructStoragePackingRule { + fn name(&self) -> &str { + "vyper-struct-storage-packing" + } + + fn description(&self) -> &str { + "Detects struct definitions with unpacked storage slots where reordering fields could save gas." + } + + fn check(&self, contract: &VyperContract) -> Vec { + let mut violations = Vec::new(); + + for struct_def in &contract.structs { + if struct_def.fields.len() < 2 { + continue; + } + + let current_slots = calculate_storage_slots(&struct_def.fields); + let reordered_fields = find_optimal_ordering(&struct_def.fields); + let optimal_slots = calculate_storage_slots(&reordered_fields); + + if optimal_slots < current_slots { + let saved_slots = current_slots - optimal_slots; + let suggestion = generate_suggestion( + &struct_def.name, + &struct_def.fields, + &reordered_fields, + saved_slots, + ); + + violations.push(RuleViolation { + rule_name: self.name().to_string(), + description: format!( + "Struct '{}' has unpacked storage slots. Current: {} slots, Optimal: {} slots (saves {}).", + struct_def.name, current_slots, optimal_slots, saved_slots + ), + severity: ViolationSeverity::Warning, + line_number: struct_def.line_number, + column_number: 1, + variable_name: struct_def.name.clone(), + suggestion, + }); + } + } + + violations + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_vyper_type_size() { + assert_eq!(get_vyper_type_size("uint8"), 1); + assert_eq!(get_vyper_type_size("uint16"), 2); + assert_eq!(get_vyper_type_size("uint256"), 32); + assert_eq!(get_vyper_type_size("uint"), 32); + assert_eq!(get_vyper_type_size("bool"), 1); + assert_eq!(get_vyper_type_size("address"), 20); + assert_eq!(get_vyper_type_size("bytes1"), 1); + assert_eq!(get_vyper_type_size("bytes32"), 32); + } + + #[test] + fn test_is_vyper_packable_type() { + assert!(is_vyper_packable_type("uint8")); + assert!(is_vyper_packable_type("uint128")); + assert!(is_vyper_packable_type("bool")); + assert!(is_vyper_packable_type("address")); + assert!(!is_vyper_packable_type("uint256")); + assert!(!is_vyper_packable_type("bytes32")); + assert!(!is_vyper_packable_type("string")); + assert!(!is_vyper_packable_type("uint8[]")); + } + + #[test] + fn test_calculate_storage_slots() { + let fields = vec![ + crate::vyper::parser::VyperStructField { + name: "flag1".to_string(), + type_name: "bool".to_string(), + size_bytes: 1, + line_number: 1, + }, + crate::vyper::parser::VyperStructField { + name: "flag2".to_string(), + type_name: "bool".to_string(), + size_bytes: 1, + line_number: 2, + }, + ]; + + assert_eq!(calculate_storage_slots(&fields), 1); + } + + #[test] + fn test_find_optimal_ordering() { + let fields = vec![ + crate::vyper::parser::VyperStructField { + name: "flag1".to_string(), + type_name: "bool".to_string(), + size_bytes: 1, + line_number: 1, + }, + crate::vyper::parser::VyperStructField { + name: "value".to_string(), + type_name: "uint256".to_string(), + size_bytes: 32, + line_number: 2, + }, + crate::vyper::parser::VyperStructField { + name: "flag2".to_string(), + type_name: "bool".to_string(), + size_bytes: 1, + line_number: 3, + }, + ]; + + let optimal = find_optimal_ordering(&fields); + let flag_positions: Vec = optimal + .iter() + .enumerate() + .filter(|(_, f)| f.name.starts_with("flag")) + .map(|(i, _)| i) + .collect(); + + assert_eq!(flag_positions, vec![0, 1]); + } + + #[test] + fn test_detect_unpacked_struct() { + let source = r#" +struct User: + is_active: bool + balance: uint256 + nonce: uint8 +"#; + let contract = VyperContract::parse(source).unwrap(); + let rule = StructStoragePackingRule; + let violations = rule.check(&contract); + + assert_eq!(violations.len(), 1); + assert!(violations[0].description.contains("User")); + } + + #[test] + fn test_no_violation_for_optimal_struct() { + let source = r#" +struct User: + nonce: uint8 + is_active: bool + balance: uint256 +"#; + let contract = VyperContract::parse(source).unwrap(); + let rule = StructStoragePackingRule; + let violations = rule.check(&contract); + + assert_eq!(violations.len(), 0); + } + + #[test] + fn test_multiple_structs() { + let source = r#" +struct User: + active: bool + balance: uint256 + +struct Config: + enabled: bool + value: uint256 +"#; + let contract = VyperContract::parse(source).unwrap(); + let rule = StructStoragePackingRule; + let violations = rule.check(&contract); + + assert_eq!(violations.len(), 2); + } + + #[test] + fn test_suggestion_output() { + let source = r#" +struct User: + is_active: bool + balance: uint256 + nonce: uint8 +"#; + let contract = VyperContract::parse(source).unwrap(); + let rule = StructStoragePackingRule; + let violations = rule.check(&contract); + + assert_eq!(violations.len(), 1); + assert!(violations[0].suggestion.contains("Suggested ordering")); + assert!(violations[0].suggestion.contains("is_active")); + assert!(violations[0].suggestion.contains("nonce")); + } +}