From e0026609adc2a02d58415a1dd4f332673363e32e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 18 Sep 2024 15:01:28 +0000 Subject: [PATCH 001/160] chore: init stylus project --- .../ethereum/sdk/stylus/.cargo/config.toml | 16 + .../ethereum/sdk/stylus/.env.example | 3 + .../stylus/.github/pull_request_template.md | 12 + target_chains/ethereum/sdk/stylus/.gitignore | 2 + target_chains/ethereum/sdk/stylus/Cargo.lock | 4220 +++++++++++++++++ target_chains/ethereum/sdk/stylus/Cargo.toml | 40 + target_chains/ethereum/sdk/stylus/README.md | 205 + .../ethereum/sdk/stylus/examples/counter.rs | 78 + target_chains/ethereum/sdk/stylus/header.png | Bin 0 -> 335346 bytes .../ethereum/sdk/stylus/licenses/Apache-2.0 | 201 + .../ethereum/sdk/stylus/licenses/COPYRIGHT.md | 5 + .../ethereum/sdk/stylus/licenses/DCO.txt | 34 + .../ethereum/sdk/stylus/licenses/MIT | 21 + .../ethereum/sdk/stylus/rust-toolchain.toml | 2 + target_chains/ethereum/sdk/stylus/src/lib.rs | 68 + target_chains/ethereum/sdk/stylus/src/main.rs | 6 + 16 files changed, 4913 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/.cargo/config.toml create mode 100644 target_chains/ethereum/sdk/stylus/.env.example create mode 100644 target_chains/ethereum/sdk/stylus/.github/pull_request_template.md create mode 100644 target_chains/ethereum/sdk/stylus/.gitignore create mode 100644 target_chains/ethereum/sdk/stylus/Cargo.lock create mode 100644 target_chains/ethereum/sdk/stylus/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/README.md create mode 100644 target_chains/ethereum/sdk/stylus/examples/counter.rs create mode 100644 target_chains/ethereum/sdk/stylus/header.png create mode 100644 target_chains/ethereum/sdk/stylus/licenses/Apache-2.0 create mode 100644 target_chains/ethereum/sdk/stylus/licenses/COPYRIGHT.md create mode 100644 target_chains/ethereum/sdk/stylus/licenses/DCO.txt create mode 100644 target_chains/ethereum/sdk/stylus/licenses/MIT create mode 100644 target_chains/ethereum/sdk/stylus/rust-toolchain.toml create mode 100644 target_chains/ethereum/sdk/stylus/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/.cargo/config.toml b/target_chains/ethereum/sdk/stylus/.cargo/config.toml new file mode 100644 index 0000000000..8c46df4b48 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.cargo/config.toml @@ -0,0 +1,16 @@ +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=-zstack-size=32768", +] + +[target.aarch64-apple-darwin] +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] + +[target.x86_64-apple-darwin] +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] diff --git a/target_chains/ethereum/sdk/stylus/.env.example b/target_chains/ethereum/sdk/stylus/.env.example new file mode 100644 index 0000000000..63e3e4d093 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.env.example @@ -0,0 +1,3 @@ +RPC_URL= +STYLUS_CONTRACT_ADDRESS= +PRIV_KEY_PATH= diff --git a/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md b/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md new file mode 100644 index 0000000000..645ad6466c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Description + +Please provide a summary of the changes and any backward incompatibilities. + +## Checklist + +- [ ] I have documented these changes where necessary. +- [ ] I have read the [DCO][DCO] and ensured that these changes comply. +- [ ] I assign this work under its [open source licensing][terms]. + +[DCO]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/DCO.txt +[terms]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/COPYRIGHT.md diff --git a/target_chains/ethereum/sdk/stylus/.gitignore b/target_chains/ethereum/sdk/stylus/.gitignore new file mode 100644 index 0000000000..fedaa2b1d2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.gitignore @@ -0,0 +1,2 @@ +/target +.env diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock new file mode 100644 index 0000000000..00d9710a15 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -0,0 +1,4220 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.50", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.50", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + +[[package]] +name = "auto_impl" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.22", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "coins-bip32" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2 0.12.2", + "rand", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-core" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "digest 0.10.7", + "generic-array", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2", + "sha3", + "thiserror", +] + +[[package]] +name = "const-hex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d59688ad0945eaf6b84cb44fedbe93484c81b48970e98f09db8a22832d7961" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[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", + "spki", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[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", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" +dependencies = [ + "base64 0.21.7", + "bytes", + "hex", + "k256", + "log", + "rand", + "rlp", + "serde", + "sha3", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2 0.11.0", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid", +] + +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c7cd562832e2ff584fa844cd2f6e5d4f35bbe11b28c7c9b8df957b2e1d0c701" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", + "ethers-solc", +] + +[[package]] +name = "ethers-addressbook" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35dc9a249c066d17e8947ff52a4116406163cf92c7f0763cb8c001760b26403f" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-contract" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43304317c7f776876e47f2f637859f6d0701c1ec7930a150f169d5fbe7d76f5a" +dependencies = [ + "const-hex", + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f96502317bf34f6d71a3e3d270defaa9485d754d789e15a8e04a84161c95eb" +dependencies = [ + "Inflector", + "const-hex", + "dunce", + "ethers-core", + "ethers-etherscan", + "eyre", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "syn 2.0.50", + "toml", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452ff6b0a64507ce8d67ffd48b1da3b42f03680dcf5382244e9c93822cbbf5de" +dependencies = [ + "Inflector", + "const-hex", + "ethers-contract-abigen", + "ethers-core", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.50", +] + +[[package]] +name = "ethers-core" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab3cef6cc1c9fd7f787043c81ad3052eff2b96a3878ef1526aa446311bdbfc9" +dependencies = [ + "arrayvec", + "bytes", + "cargo_metadata", + "chrono", + "const-hex", + "elliptic-curve", + "ethabi", + "generic-array", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "rand", + "rlp", + "serde", + "serde_json", + "strum", + "syn 2.0.50", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "ethers-etherscan" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d45b981f5fa769e1d0343ebc2a44cfa88c9bc312eb681b676318b40cef6fb1" +dependencies = [ + "chrono", + "ethers-core", + "reqwest", + "semver 1.0.22", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-middleware" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145211f34342487ef83a597c1e69f0d3e01512217a7c72cc8a25931854c7dca0" +dependencies = [ + "async-trait", + "auto_impl", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-providers", + "ethers-signers", + "futures-channel", + "futures-locks", + "futures-util", + "instant", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ethers-providers" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6b15393996e3b8a78ef1332d6483c11d839042c17be58decc92fa8b1c3508a" +dependencies = [ + "async-trait", + "auto_impl", + "base64 0.21.7", + "bytes", + "const-hex", + "enr", + "ethers-core", + "futures-core", + "futures-timer", + "futures-util", + "hashers", + "http", + "instant", + "jsonwebtoken", + "once_cell", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "ws_stream_wasm", +] + +[[package]] +name = "ethers-signers" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b125a103b56aef008af5d5fb48191984aa326b50bfd2557d231dc499833de3" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "const-hex", + "elliptic-curve", + "eth-keystore", + "ethers-core", + "rand", + "sha2", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-solc" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d21df08582e0a43005018a858cc9b465c5fff9cf4056651be64f844e57d1f55f" +dependencies = [ + "cfg-if 1.0.0", + "const-hex", + "dirs", + "dunce", + "ethers-core", + "glob", + "home", + "md-5", + "num_cpus", + "once_cell", + "path-slash", + "rayon", + "regex", + "semver 1.0.22", + "serde", + "serde_json", + "solang-parser", + "svm-rs", + "thiserror", + "tiny-keccak", + "tokio", + "tracing", + "walkdir", + "yansi", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +dependencies = [ + "gloo-timers", + "send_wrapper 0.4.0", +] + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[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 = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[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 = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.7", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lalrpop" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.7.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "mini-alloc" +version = "0.5.2" +source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate 2.0.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +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 = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn 2.0.50", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.2", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.2", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +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 = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rlp-derive", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.22", +] + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[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 = "scale-info" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +dependencies = [ + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2 0.11.0", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" +dependencies = [ + "cc", + "cfg-if 1.0.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +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 = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "solang-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" +dependencies = [ + "itertools 0.11.0", + "lalrpop", + "lalrpop-util", + "phf", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[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 = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.50", +] + +[[package]] +name = "stylus-hello-world" +version = "0.1.9" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "dotenv", + "ethers", + "eyre", + "hex", + "mini-alloc 0.4.2", + "stylus-sdk", + "tokio", +] + +[[package]] +name = "stylus-proc" +version = "0.5.2" +source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.5.2" +source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "mini-alloc 0.5.2", + "regex", + "stylus-proc", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "svm-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" +dependencies = [ + "dirs", + "fs2", + "hex", + "once_cell", + "reqwest", + "semver 1.0.22", + "serde", + "serde_json", + "sha2", + "thiserror", + "url", + "zip", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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 = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.6", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.2", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.50", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.0", + "send_wrapper 0.6.0", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml new file mode 100644 index 0000000000..7316ea878d --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "stylus-hello-world" +version = "0.1.9" +edition = "2021" +license = "MIT OR Apache-2.0" +homepage = "https://github.com/OffchainLabs/stylus-hello-world" +repository = "https://github.com/OffchainLabs/stylus-hello-world" +keywords = ["arbitrum", "ethereum", "stylus", "alloy"] +description = "Stylus hello world example" + +[dependencies] +alloy-primitives = "=0.7.6" +alloy-sol-types = "=0.7.6" +mini-alloc = "0.4.2" +stylus-sdk = "0.6.0" +hex = "0.4.3" +dotenv = "0.15.0" + +[dev-dependencies] +tokio = { version = "1.12.0", features = ["full"] } +ethers = "2.0" +eyre = "0.6.8" + +[features] +export-abi = ["stylus-sdk/export-abi"] +debug = ["stylus-sdk/debug"] + +[[bin]] +name = "stylus-hello-world" +path = "src/main.rs" + +[lib] +crate-type = ["lib", "cdylib"] + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" +opt-level = "s" diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md new file mode 100644 index 0000000000..ead4de69fd --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -0,0 +1,205 @@ +![Image](./header.png) + +# Stylus Hello World + +Project starter template for writing Arbitrum Stylus programs in Rust using the [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs). It includes a Rust implementation of a basic counter Ethereum smart contract: + +```js +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} +``` + +To set up more minimal example that still uses the Stylus SDK, use `cargo stylus new --minimal ` under [OffchainLabs/cargo-stylus](https://github.com/OffchainLabs/cargo-stylus). + +## Quick Start + +Install [Rust](https://www.rust-lang.org/tools/install), and then install the Stylus CLI tool with Cargo + +```bash +cargo install --force cargo-stylus cargo-stylus-check +``` + +Add the `wasm32-unknown-unknown` build target to your Rust compiler: + +``` +rustup target add wasm32-unknown-unknown +``` + +You should now have it available as a Cargo subcommand: + +```bash +cargo stylus --help +``` + +Then, clone the template: + +``` +git clone https://github.com/OffchainLabs/stylus-hello-world && cd stylus-hello-world +``` + +### Testnet Information + +All testnet information, including faucets and RPC endpoints can be found [here](https://docs.arbitrum.io/stylus/reference/testnet-information). + +### ABI Export + +You can export the Solidity ABI for your program by using the `cargo stylus` tool as follows: + +```bash +cargo stylus export-abi +``` + +which outputs: + +```js +/** + * This file was automatically generated by Stylus and represents a Rust program. + * For more information, please see [The Stylus SDK](https://github.com/OffchainLabs/stylus-sdk-rs). + */ + +interface Counter { + function setNumber(uint256 new_number) external; + + function increment() external; +} +``` + +Exporting ABIs uses a feature that is enabled by default in your Cargo.toml: + +```toml +[features] +export-abi = ["stylus-sdk/export-abi"] +``` + +## Deploying + +You can use the `cargo stylus` command to also deploy your program to the Stylus testnet. We can use the tool to first check +our program compiles to valid WASM for Stylus and will succeed a deployment onchain without transacting. By default, this will use the Stylus testnet public RPC endpoint. See here for [Stylus testnet information](https://docs.arbitrum.io/stylus/reference/testnet-information) + +```bash +cargo stylus check +``` + +If successful, you should see: + +```bash +Finished release [optimized] target(s) in 1.88s +Reading WASM file at stylus-hello-world/target/wasm32-unknown-unknown/release/stylus-hello-world.wasm +Compressed WASM size: 8.9 KB +Program succeeded Stylus onchain activation checks with Stylus version: 1 +``` + +Next, we can estimate the gas costs to deploy and activate our program before we send our transaction. Check out the [cargo-stylus](https://github.com/OffchainLabs/cargo-stylus) README to see the different wallet options for this step: + +```bash +cargo stylus deploy \ + --private-key-path= \ + --estimate-gas +``` + +You will then see the estimated gas cost for deploying before transacting: + +```bash +Deploying program to address e43a32b54e48c7ec0d3d9ed2d628783c23d65020 +Estimated gas for deployment: 1874876 +``` + +The above only estimates gas for the deployment tx by default. To estimate gas for activation, first deploy your program using `--mode=deploy-only`, and then run `cargo stylus deploy` with the `--estimate-gas` flag, `--mode=activate-only`, and specify `--activate-program-address`. + + +Here's how to deploy: + +```bash +cargo stylus deploy \ + --private-key-path= +``` + +The CLI will send 2 transactions to deploy and activate your program onchain. + +```bash +Compressed WASM size: 8.9 KB +Deploying program to address 0x457b1ba688e9854bdbed2f473f7510c476a3da09 +Estimated gas: 1973450 +Submitting tx... +Confirmed tx 0x42db…7311, gas used 1973450 +Activating program at address 0x457b1ba688e9854bdbed2f473f7510c476a3da09 +Estimated gas: 14044638 +Submitting tx... +Confirmed tx 0x0bdb…3307, gas used 14044638 +``` + +Once both steps are successful, you can interact with your program as you would with any Ethereum smart contract. + +## Calling Your Program + +This template includes an example of how to call and transact with your program in Rust using [ethers-rs](https://github.com/gakonst/ethers-rs) under the `examples/counter.rs`. However, your programs are also Ethereum ABI equivalent if using the Stylus SDK. **They can be called and transacted with using any other Ethereum tooling.** + +By using the program address from your deployment step above, and your wallet, you can attempt to call the counter program and increase its value in storage: + +```rs +abigen!( + Counter, + r#"[ + function number() external view returns (uint256) + function setNumber(uint256 number) external + function increment() external + ]"# +); +let counter = Counter::new(address, client); +let num = counter.number().call().await; +println!("Counter number value = {:?}", num); + +let _ = counter.increment().send().await?.await?; +println!("Successfully incremented counter via a tx"); + +let num = counter.number().call().await; +println!("New counter number value = {:?}", num); +``` + +Before running, set the following env vars or place them in a `.env` file (see: [.env.example](./.env.example)) in this project: + +``` +RPC_URL=https://sepolia-rollup.arbitrum.io/rpc +STYLUS_CONTRACT_ADDRESS= +PRIV_KEY_PATH= +``` + +Next, run: + +``` +cargo run --example counter --target= +``` + +Where you can find `YOUR_ARCHITECTURE` by running `rustc -vV | grep host`. For M1 Apple computers, for example, this is `aarch64-apple-darwin` and for most Linux x86 it is `x86_64-unknown-linux-gnu` + +## Build Options + +By default, the cargo stylus tool will build your project for WASM using sensible optimizations, but you can control how this gets compiled by seeing the full README for [cargo stylus](https://github.com/OffchainLabs/cargo-stylus). If you wish to optimize the size of your compiled WASM, see the different options available [here](https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md). + +## Peeking Under the Hood + +The [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs) contains many features for writing Stylus programs in Rust. It also provides helpful macros to make the experience for Solidity developers easier. These macros expand your code into pure Rust code that can then be compiled to WASM. If you want to see what the `stylus-hello-world` boilerplate expands into, you can use `cargo expand` to see the pure Rust code that will be deployed onchain. + +First, run `cargo install cargo-expand` if you don't have the subcommand already, then: + +``` +cargo expand --all-features --release --target= +``` + +Where you can find `YOUR_ARCHITECTURE` by running `rustc -vV | grep host`. For M1 Apple computers, for example, this is `aarch64-apple-darwin`. + +## License + +This project is fully open source, including an Apache-2.0 or MIT license at your choosing under your own copyright. diff --git a/target_chains/ethereum/sdk/stylus/examples/counter.rs b/target_chains/ethereum/sdk/stylus/examples/counter.rs new file mode 100644 index 0000000000..9414df2053 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/counter.rs @@ -0,0 +1,78 @@ +//! Example on how to interact with a deployed `stylus-hello-world` contract using defaults. +//! This example uses ethers-rs to instantiate the contract using a Solidity ABI. +//! Then, it attempts to check the current counter value, increment it via a tx, +//! and check the value again. The deployed contract is fully written in Rust and compiled to WASM +//! but with Stylus, it is accessible just as a normal Solidity smart contract is via an ABI. + +use ethers::{ + middleware::SignerMiddleware, + prelude::abigen, + providers::{Http, Middleware, Provider}, + signers::{LocalWallet, Signer}, + types::Address, +}; +use dotenv::dotenv; +use eyre::eyre; +use std::io::{BufRead, BufReader}; +use std::str::FromStr; +use std::sync::Arc; + +/// Your private key file path. +const PRIV_KEY_PATH: &str = "PRIV_KEY_PATH"; + +/// Stylus RPC endpoint url. +const RPC_URL: &str = "RPC_URL"; + +/// Deployed pragram address. +const STYLUS_CONTRACT_ADDRESS: &str = "STYLUS_CONTRACT_ADDRESS"; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenv().ok(); + let priv_key_path = + std::env::var(PRIV_KEY_PATH).map_err(|_| eyre!("No {} env var set", PRIV_KEY_PATH))?; + let rpc_url = std::env::var(RPC_URL).map_err(|_| eyre!("No {} env var set", RPC_URL))?; + let contract_address = std::env::var(STYLUS_CONTRACT_ADDRESS) + .map_err(|_| eyre!("No {} env var set", STYLUS_CONTRACT_ADDRESS))?; + abigen!( + Counter, + r#"[ + function number() external view returns (uint256) + function setNumber(uint256 number) external + function increment() external + ]"# + ); + + let provider = Provider::::try_from(rpc_url)?; + let address: Address = contract_address.parse()?; + + let privkey = read_secret_from_file(&priv_key_path)?; + let wallet = LocalWallet::from_str(&privkey)?; + let chain_id = provider.get_chainid().await?.as_u64(); + let client = Arc::new(SignerMiddleware::new( + provider, + wallet.clone().with_chain_id(chain_id), + )); + + let counter = Counter::new(address, client); + let num = counter.number().call().await; + println!("Counter number value = {:?}", num); + + let pending = counter.increment(); + if let Some(receipt) = pending.send().await?.await? { + println!("Receipt = {:?}", receipt); + } + println!("Successfully incremented counter via a tx"); + + let num = counter.number().call().await; + println!("New counter number value = {:?}", num); + Ok(()) +} + +fn read_secret_from_file(fpath: &str) -> eyre::Result { + let f = std::fs::File::open(fpath)?; + let mut buf_reader = BufReader::new(f); + let mut secret = String::new(); + buf_reader.read_line(&mut secret)?; + Ok(secret.trim().to_string()) +} diff --git a/target_chains/ethereum/sdk/stylus/header.png b/target_chains/ethereum/sdk/stylus/header.png new file mode 100644 index 0000000000000000000000000000000000000000..dd12f4675db336da39b0f36ce92b9e9876eead50 GIT binary patch literal 335346 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 z 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 z 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 U256 { + self.number.get() + } + + /// Sets a number in storage to a user-specified value. + pub fn set_number(&mut self, new_number: U256) { + self.number.set(new_number); + } + + /// Sets a number in storage to a user-specified value. + pub fn mul_number(&mut self, new_number: U256) { + self.number.set(new_number * self.number.get()); + } + + /// Sets a number in storage to a user-specified value. + pub fn add_number(&mut self, new_number: U256) { + self.number.set(new_number + self.number.get()); + } + + /// Increments `number` and updates its value in storage. + pub fn increment(&mut self) { + let number = self.number.get(); + self.set_number(number + U256::from(1)); + } +} diff --git a/target_chains/ethereum/sdk/stylus/src/main.rs b/target_chains/ethereum/sdk/stylus/src/main.rs new file mode 100644 index 0000000000..3bbccf9cb5 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/src/main.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(feature = "export-abi"), no_main)] + +#[cfg(feature = "export-abi")] +fn main() { + stylus_hello_world::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;"); +} From 8d19bc11107cd85562a9a0566ee4cfe3105df829 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 18 Sep 2024 18:32:35 +0000 Subject: [PATCH 002/160] chore: added scripts --- .../ethereum/sdk/stylus/scripts/bench.sh | 13 ++++ .../ethereum/sdk/stylus/scripts/check-wasm.sh | 32 ++++++++++ .../ethereum/sdk/stylus/scripts/e2e-tests.sh | 14 +++++ .../sdk/stylus/scripts/nitro-testnode.sh | 59 +++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100755 target_chains/ethereum/sdk/stylus/scripts/bench.sh create mode 100755 target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh create mode 100755 target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh create mode 100755 target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh new file mode 100755 index 0000000000..8878daec23 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +MYDIR=$(realpath "$(dirname "$0")") +cd "$MYDIR" +cd .. + +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly} +cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort + +export RPC_URL=http://localhost:8547 +cargo run --release -p benches +echo "Finished running benches!" diff --git a/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh new file mode 100755 index 0000000000..9ec839dd00 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e + +mydir=$(dirname "$0") +cd "$mydir" || exit +cd .. + +# Check contract wasm binary by crate name +check_wasm () { + local CONTRACT_CRATE_NAME=$1 + local CONTRACT_BIN_NAME="${CONTRACT_CRATE_NAME//-/_}.wasm" + + echo + echo "Checking contract $CONTRACT_CRATE_NAME" + cargo stylus check --wasm-file ./target/wasm32-unknown-unknown/release/"$CONTRACT_BIN_NAME" +} + +# Retrieve all alphanumeric contract's crate names in `./examples` directory. +get_example_crate_names () { + # shellcheck disable=SC2038 + # NOTE: optimistically relying on the 'name = ' string at Cargo.toml file + find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs grep 'name = ' | grep -oE '".*"' | tr -d "'\"" +} + +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly} + +cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort + +for CRATE_NAME in $(get_example_crate_names) +do + check_wasm "$CRATE_NAME" +done diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh new file mode 100755 index 0000000000..f7b9cdeb1c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +MYDIR=$(realpath "$(dirname "$0")") +cd "$MYDIR" +cd .. + +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} +cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort + +export RPC_URL=http://localhost:8547 +# We should use stable here once nitro-testnode is updated and the contracts fit +# the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" diff --git a/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh new file mode 100755 index 0000000000..815323c242 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +MYDIR=$(realpath "$(dirname "$0")") +cd "$MYDIR" || exit + +HAS_INIT=false +HAS_DETACH=false + +while [[ $# -gt 0 ]] +do + case "$1" in + -i|--init) + HAS_INIT=true + shift + ;; + -d|--detach) + HAS_DETACH=true + shift + ;; + -q|--quit) + docker container stop $(docker container ls -q --filter name=nitro-testnode) + exit 0 + ;; + *) + echo "OPTIONS:" + echo "-i|--init: clone repo and init nitro test node" + echo "-d|--detach: setup nitro test node in detached mode" + echo "-q|--quit: shutdown nitro test node docker containers" + exit 0 + ;; + esac +done + +TEST_NODE_DIR="$MYDIR/../nitro-testnode" +if [ ! -d "$TEST_NODE_DIR" ]; then + HAS_INIT=true +fi + +if $HAS_INIT +then + cd "$MYDIR" || exit + cd .. + + git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git + cd ./nitro-testnode || exit + # `release` branch. + git checkout 8cb6b84e31909157d431e7e4af9fb83799443e00 || exit + + ./test-node.bash --no-run --init || exit +fi + + +cd "$TEST_NODE_DIR" || exit +if $HAS_DETACH +then + ./test-node.bash --detach +else + ./test-node.bash +fi From 08c010cc5933b46bbfa5b8be614c52bbee1ad392 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 18 Sep 2024 19:32:46 +0000 Subject: [PATCH 003/160] chore: created workspace --- .../ethereum/sdk/stylus/.cargo/config.toml | 14 +- .../ethereum/sdk/stylus/.env.example | 3 - target_chains/ethereum/sdk/stylus/Cargo.lock | 4220 ----------------- target_chains/ethereum/sdk/stylus/Cargo.toml | 63 +- target_chains/ethereum/sdk/stylus/README.md | 222 +- .../ethereum/sdk/stylus/contracts/Cargo.toml | 13 + .../ethereum/sdk/stylus/contracts/src/lib.rs | 0 .../ethereum/sdk/stylus/examples/counter.rs | 78 - target_chains/ethereum/sdk/stylus/header.png | Bin 335346 -> 0 bytes target_chains/ethereum/sdk/stylus/src/lib.rs | 68 - target_chains/ethereum/sdk/stylus/src/main.rs | 6 - 11 files changed, 116 insertions(+), 4571 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/.env.example delete mode 100644 target_chains/ethereum/sdk/stylus/Cargo.lock create mode 100644 target_chains/ethereum/sdk/stylus/contracts/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/examples/counter.rs delete mode 100644 target_chains/ethereum/sdk/stylus/header.png delete mode 100644 target_chains/ethereum/sdk/stylus/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/.cargo/config.toml b/target_chains/ethereum/sdk/stylus/.cargo/config.toml index 8c46df4b48..77f73804f6 100644 --- a/target_chains/ethereum/sdk/stylus/.cargo/config.toml +++ b/target_chains/ethereum/sdk/stylus/.cargo/config.toml @@ -1,16 +1,8 @@ [target.wasm32-unknown-unknown] -rustflags = [ - "-C", "link-arg=-zstack-size=32768", -] +rustflags = ["-C", "link-arg=-zstack-size=8192"] [target.aarch64-apple-darwin] -rustflags = [ -"-C", "link-arg=-undefined", -"-C", "link-arg=dynamic_lookup", -] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] [target.x86_64-apple-darwin] -rustflags = [ -"-C", "link-arg=-undefined", -"-C", "link-arg=dynamic_lookup", -] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/.env.example b/target_chains/ethereum/sdk/stylus/.env.example deleted file mode 100644 index 63e3e4d093..0000000000 --- a/target_chains/ethereum/sdk/stylus/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -RPC_URL= -STYLUS_CONTRACT_ADDRESS= -PRIV_KEY_PATH= diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock deleted file mode 100644 index 00d9710a15..0000000000 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ /dev/null @@ -1,4220 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloy-primitives" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" -dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if 1.0.0", - "const-hex", - "derive_more", - "hex-literal", - "itoa", - "k256", - "keccak-asm", - "proptest", - "rand", - "ruint", - "serde", - "tiny-keccak", -] - -[[package]] -name = "alloy-rlp" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" -dependencies = [ - "arrayvec", - "bytes", -] - -[[package]] -name = "alloy-sol-macro" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" -dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "alloy-sol-macro-expander" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" -dependencies = [ - "alloy-sol-macro-input", - "const-hex", - "heck 0.5.0", - "indexmap", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.50", - "syn-solidity", - "tiny-keccak", -] - -[[package]] -name = "alloy-sol-macro-input" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" -dependencies = [ - "const-hex", - "dunce", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.50", - "syn-solidity", -] - -[[package]] -name = "alloy-sol-types" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" -dependencies = [ - "alloy-primitives", - "alloy-sol-macro", - "const-hex", - "serde", -] - -[[package]] -name = "ark-ff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" -dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest 0.10.7", - "itertools 0.10.5", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.4.0", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" -dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-serialize" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" -dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-std" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "ark-std" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "async-trait" -version = "0.1.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version 0.4.0", -] - -[[package]] -name = "auto_impl" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" -dependencies = [ - "sha2", - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" -dependencies = [ - "serde", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.22", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cc" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "coins-bip32" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" -dependencies = [ - "bs58", - "coins-core", - "digest 0.10.7", - "hmac", - "k256", - "serde", - "sha2", - "thiserror", -] - -[[package]] -name = "coins-bip39" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" -dependencies = [ - "bitvec", - "coins-bip32", - "hmac", - "once_cell", - "pbkdf2 0.12.2", - "rand", - "sha2", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" -dependencies = [ - "base64 0.21.7", - "bech32", - "bs58", - "digest 0.10.7", - "generic-array", - "hex", - "ripemd", - "serde", - "serde_derive", - "sha2", - "sha3", - "thiserror", -] - -[[package]] -name = "const-hex" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d59688ad0945eaf6b84cb44fedbe93484c81b48970e98f09db8a22832d7961" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "hex", - "proptest", - "serde", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -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.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "data-encoding" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" - -[[package]] -name = "der" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - -[[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", - "spki", -] - -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[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", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ena" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" -dependencies = [ - "log", -] - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "enr" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" -dependencies = [ - "base64 0.21.7", - "bytes", - "hex", - "k256", - "log", - "rand", - "rlp", - "serde", - "sha3", - "zeroize", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "eth-keystore" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" -dependencies = [ - "aes", - "ctr", - "digest 0.10.7", - "hex", - "hmac", - "pbkdf2 0.11.0", - "rand", - "scrypt", - "serde", - "serde_json", - "sha2", - "sha3", - "thiserror", - "uuid", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c7cd562832e2ff584fa844cd2f6e5d4f35bbe11b28c7c9b8df957b2e1d0c701" -dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", - "ethers-solc", -] - -[[package]] -name = "ethers-addressbook" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35dc9a249c066d17e8947ff52a4116406163cf92c7f0763cb8c001760b26403f" -dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", -] - -[[package]] -name = "ethers-contract" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43304317c7f776876e47f2f637859f6d0701c1ec7930a150f169d5fbe7d76f5a" -dependencies = [ - "const-hex", - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "futures-util", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f96502317bf34f6d71a3e3d270defaa9485d754d789e15a8e04a84161c95eb" -dependencies = [ - "Inflector", - "const-hex", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "reqwest", - "serde", - "serde_json", - "syn 2.0.50", - "toml", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452ff6b0a64507ce8d67ffd48b1da3b42f03680dcf5382244e9c93822cbbf5de" -dependencies = [ - "Inflector", - "const-hex", - "ethers-contract-abigen", - "ethers-core", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.50", -] - -[[package]] -name = "ethers-core" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab3cef6cc1c9fd7f787043c81ad3052eff2b96a3878ef1526aa446311bdbfc9" -dependencies = [ - "arrayvec", - "bytes", - "cargo_metadata", - "chrono", - "const-hex", - "elliptic-curve", - "ethabi", - "generic-array", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "rand", - "rlp", - "serde", - "serde_json", - "strum", - "syn 2.0.50", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d45b981f5fa769e1d0343ebc2a44cfa88c9bc312eb681b676318b40cef6fb1" -dependencies = [ - "chrono", - "ethers-core", - "reqwest", - "semver 1.0.22", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-middleware" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145211f34342487ef83a597c1e69f0d3e01512217a7c72cc8a25931854c7dca0" -dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-channel", - "futures-locks", - "futures-util", - "instant", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", -] - -[[package]] -name = "ethers-providers" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6b15393996e3b8a78ef1332d6483c11d839042c17be58decc92fa8b1c3508a" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.7", - "bytes", - "const-hex", - "enr", - "ethers-core", - "futures-core", - "futures-timer", - "futures-util", - "hashers", - "http", - "instant", - "jsonwebtoken", - "once_cell", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite", - "tracing", - "tracing-futures", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethers-signers" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b125a103b56aef008af5d5fb48191984aa326b50bfd2557d231dc499833de3" -dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "const-hex", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "rand", - "sha2", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-solc" -version = "2.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21df08582e0a43005018a858cc9b465c5fff9cf4056651be64f844e57d1f55f" -dependencies = [ - "cfg-if 1.0.0", - "const-hex", - "dirs", - "dunce", - "ethers-core", - "glob", - "home", - "md-5", - "num_cpus", - "once_cell", - "path-slash", - "rayon", - "regex", - "semver 1.0.22", - "serde", - "serde_json", - "solang-parser", - "svm-rs", - "thiserror", - "tiny-keccak", - "tokio", - "tracing", - "walkdir", - "yansi", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "fastrlp" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[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 = "h2" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[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 = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[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.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "js-sys" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.7", - "pem", - "ring 0.16.20", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "k256" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" -dependencies = [ - "cfg-if 1.0.0", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "keccak-asm" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" -dependencies = [ - "digest 0.10.7", - "sha3-asm", -] - -[[package]] -name = "keccak-const" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" - -[[package]] -name = "lalrpop" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" -dependencies = [ - "ascii-canvas", - "bit-set", - "diff", - "ena", - "is-terminal", - "itertools 0.10.5", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax 0.7.5", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.2", - "libc", - "redox_syscall", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if 1.0.0", - "digest 0.10.7", -] - -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mini-alloc" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" -dependencies = [ - "cfg-if 1.0.0", - "wee_alloc", -] - -[[package]] -name = "mini-alloc" -version = "0.5.2" -source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "parity-scale-codec" -version = "3.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" -dependencies = [ - "proc-macro-crate 2.0.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "path-slash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.7", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac", -] - -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version 0.4.0", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -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 = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[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.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" -dependencies = [ - "proc-macro2", - "syn 2.0.50", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.4.2", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax 0.8.2", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -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 = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-automata" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "reqwest" -version = "0.11.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if 1.0.0", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rlp-derive", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ruint" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" -dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "bytes", - "fastrlp", - "num-bigint", - "num-traits", - "parity-scale-codec", - "primitive-types", - "proptest", - "rand", - "rlp", - "ruint-macro", - "serde", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.22", -] - -[[package]] -name = "rustix" -version = "0.38.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" -dependencies = [ - "log", - "ring 0.17.8", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - -[[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 = "scale-info" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scrypt" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" -dependencies = [ - "hmac", - "pbkdf2 0.11.0", - "salsa20", - "sha2", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" -dependencies = [ - "serde", -] - -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "serde_json" -version = "1.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", -] - -[[package]] -name = "sha3-asm" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" -dependencies = [ - "cc", - "cfg-if 1.0.0", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -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 = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "socket2" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "solang-parser" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" -dependencies = [ - "itertools 0.11.0", - "lalrpop", - "lalrpop-util", - "phf", - "thiserror", - "unicode-xid", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[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 = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared 0.10.0", - "precomputed-hash", -] - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.50", -] - -[[package]] -name = "stylus-hello-world" -version = "0.1.9" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "dotenv", - "ethers", - "eyre", - "hex", - "mini-alloc 0.4.2", - "stylus-sdk", - "tokio", -] - -[[package]] -name = "stylus-proc" -version = "0.5.2" -source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "cfg-if 1.0.0", - "convert_case 0.6.0", - "lazy_static", - "proc-macro2", - "quote", - "regex", - "sha3", - "syn 1.0.109", - "syn-solidity", -] - -[[package]] -name = "stylus-sdk" -version = "0.5.2" -source = "git+https://github.com/OffchainLabs/stylus-sdk-rs.git?rev=refs/pull/145/head#74593a0b0eccd119f6b870ab3421e9308a8a69a2" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "cfg-if 1.0.0", - "derivative", - "hex", - "keccak-const", - "lazy_static", - "mini-alloc 0.5.2", - "regex", - "stylus-proc", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "svm-rs" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" -dependencies = [ - "dirs", - "fs2", - "hex", - "once_cell", - "reqwest", - "semver 1.0.22", - "serde", - "serde_json", - "sha2", - "thiserror", - "url", - "zip", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn-solidity" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" -dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" -dependencies = [ - "cfg-if 1.0.0", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "time" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -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 = "tokio" -version = "1.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "rustls", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots", -] - -[[package]] -name = "tokio-util" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.6", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.2", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand", - "rustls", - "sha1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.50", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" - -[[package]] -name = "web-sys" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[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.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.3", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" -dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys 0.48.0", -] - -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version 0.4.0", - "send_wrapper 0.6.0", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "time", - "zstd", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 7316ea878d..062b6d627f 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,40 +1,57 @@ -[package] -name = "stylus-hello-world" -version = "0.1.9" +[workspace] +members = [ + "contracts", +] +default-members = [ + "contracts", +] + +# Explicitly set the resolver to version 2, which is the default for packages +# with edition >= 2021. +# https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html +resolver = "2" + +[workspace.package] +authors = ["Ifechukwu Daniel"] +license = "MIT" +keywords = ["arbitrum", "ethereum", "stylus"] +repository = "https://github.com/pyth-network/pyth-crosschain/" +version = "0.0.1" edition = "2021" -license = "MIT OR Apache-2.0" -homepage = "https://github.com/OffchainLabs/stylus-hello-world" -repository = "https://github.com/OffchainLabs/stylus-hello-world" -keywords = ["arbitrum", "ethereum", "stylus", "alloy"] -description = "Stylus hello world example" -[dependencies] +[workspace.lints.rust] +missing_docs = "error" +unreachable_pub = "warn" +rust_2021_compatibility = { level = "warn", priority = -1 } + +[workspace.lints.clippy] +pedantic = "warn" +all = "warn" + +[workspace.dependencies] alloy-primitives = "=0.7.6" alloy-sol-types = "=0.7.6" mini-alloc = "0.4.2" stylus-sdk = "0.6.0" hex = "0.4.3" dotenv = "0.15.0" - -[dev-dependencies] tokio = { version = "1.12.0", features = ["full"] } ethers = "2.0" eyre = "0.6.8" -[features] -export-abi = ["stylus-sdk/export-abi"] -debug = ["stylus-sdk/debug"] - -[[bin]] -name = "stylus-hello-world" -path = "src/main.rs" - -[lib] -crate-type = ["lib", "cdylib"] - [profile.release] codegen-units = 1 +panic = "abort" +opt-level = "z" strip = true lto = true +debug = false +rpath = false +debug-assertions = false +incremental = false + +[profile.dev] panic = "abort" -opt-level = "s" + + + diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index ead4de69fd..4d46dffe27 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -1,205 +1,103 @@ -![Image](./header.png) +# Pyth Stylus SDK -# Stylus Hello World +This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle using Solidity. Also, it contains [the Pyth Interface ABI](./abis/IPyth.json) that you can use in your libraries +to communicate with the Pyth contract. -Project starter template for writing Arbitrum Stylus programs in Rust using the [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs). It includes a Rust implementation of a basic counter Ethereum smart contract: +It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming Pyth data. -```js -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +## Installation -contract Counter { - uint256 public number; +###Truffle/Hardhat - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} -``` - -To set up more minimal example that still uses the Stylus SDK, use `cargo stylus new --minimal ` under [OffchainLabs/cargo-stylus](https://github.com/OffchainLabs/cargo-stylus). - -## Quick Start - -Install [Rust](https://www.rust-lang.org/tools/install), and then install the Stylus CLI tool with Cargo +If you are using Truffle or Hardhat, simply install the NPM package: ```bash -cargo install --force cargo-stylus cargo-stylus-check +npm install @pythnetwork/pyth-sdk-solidity ``` -Add the `wasm32-unknown-unknown` build target to your Rust compiler: - -``` -rustup target add wasm32-unknown-unknown -``` +###Foundry -You should now have it available as a Cargo subcommand: +If you are using Foundry, you will need to create an NPM project if you don't already have one. +From the root directory of your project, run: ```bash -cargo stylus --help +npm init -y +npm install @pythnetwork/pyth-sdk-solidity ``` -Then, clone the template: +Then add the following line to your `remappings.txt` file: -``` -git clone https://github.com/OffchainLabs/stylus-hello-world && cd stylus-hello-world +```text +@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity ``` -### Testnet Information +## Example Usage -All testnet information, including faucets and RPC endpoints can be found [here](https://docs.arbitrum.io/stylus/reference/testnet-information). +To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this +interface in order to use the prices safely. -### ABI Export +For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed +you're interested in. The price feeds available on each chain are listed [below](#target-chains). -You can export the Solidity ABI for your program by using the `cargo stylus` tool as follows: +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; -```bash -cargo stylus export-abi -``` +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; -which outputs: +contract ExampleContract { + IPyth pyth; -```js -/** - * This file was automatically generated by Stylus and represents a Rust program. - * For more information, please see [The Stylus SDK](https://github.com/OffchainLabs/stylus-sdk-rs). - */ + constructor(address pythContract) { + pyth = IPyth(pythContract); + } -interface Counter { - function setNumber(uint256 new_number) external; + function getBtcUsdPrice( + bytes[] calldata priceUpdateData + ) public payable returns (PythStructs.Price memory) { + // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data + // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. + // See section "How Pyth Works on EVM Chains" below for more information. + uint fee = pyth.getUpdateFee(priceUpdateData); + pyth.updatePriceFeeds{ value: fee }(priceUpdateData); - function increment() external; + bytes32 priceID = 0xf9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b; + // Read the current value of priceID, aborting the transaction if the price has not been updated in the last 10 + // seconds. + return pyth.getPriceNoOlderThan(priceID, 10); + } } -``` - -Exporting ABIs uses a feature that is enabled by default in your Cargo.toml: - -```toml -[features] -export-abi = ["stylus-sdk/export-abi"] -``` - -## Deploying - -You can use the `cargo stylus` command to also deploy your program to the Stylus testnet. We can use the tool to first check -our program compiles to valid WASM for Stylus and will succeed a deployment onchain without transacting. By default, this will use the Stylus testnet public RPC endpoint. See here for [Stylus testnet information](https://docs.arbitrum.io/stylus/reference/testnet-information) - -```bash -cargo stylus check -``` - -If successful, you should see: - -```bash -Finished release [optimized] target(s) in 1.88s -Reading WASM file at stylus-hello-world/target/wasm32-unknown-unknown/release/stylus-hello-world.wasm -Compressed WASM size: 8.9 KB -Program succeeded Stylus onchain activation checks with Stylus version: 1 -``` - -Next, we can estimate the gas costs to deploy and activate our program before we send our transaction. Check out the [cargo-stylus](https://github.com/OffchainLabs/cargo-stylus) README to see the different wallet options for this step: - -```bash -cargo stylus deploy \ - --private-key-path= \ - --estimate-gas -``` -You will then see the estimated gas cost for deploying before transacting: - -```bash -Deploying program to address e43a32b54e48c7ec0d3d9ed2d628783c23d65020 -Estimated gas for deployment: 1874876 ``` -The above only estimates gas for the deployment tx by default. To estimate gas for activation, first deploy your program using `--mode=deploy-only`, and then run `cargo stylus deploy` with the `--estimate-gas` flag, `--mode=activate-only`, and specify `--activate-program-address`. - - -Here's how to deploy: - -```bash -cargo stylus deploy \ - --private-key-path= -``` +## How Pyth Works on EVM Chains -The CLI will send 2 transactions to deploy and activate your program onchain. +Pyth prices are published on Pythnet, and relayed to EVM chains using the [Wormhole Network](https://wormholenetwork.com/) as a cross-chain message passing bridge. The Wormhole Network observes when Pyth prices on Pythnet have changed and publishes an off-chain signed message attesting to this fact. This is explained in more detail [here](https://docs.wormholenetwork.com/wormhole/). -```bash -Compressed WASM size: 8.9 KB -Deploying program to address 0x457b1ba688e9854bdbed2f473f7510c476a3da09 -Estimated gas: 1973450 -Submitting tx... -Confirmed tx 0x42db…7311, gas used 1973450 -Activating program at address 0x457b1ba688e9854bdbed2f473f7510c476a3da09 -Estimated gas: 14044638 -Submitting tx... -Confirmed tx 0x0bdb…3307, gas used 14044638 -``` +This signed message can then be submitted to the Pyth contract on the EVM networks along the required update fee for it, which will verify the Wormhole message and update the Pyth contract with the new price. -Once both steps are successful, you can interact with your program as you would with any Ethereum smart contract. +Please refer to [Pyth On-Demand Updates page](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand) for more information. -## Calling Your Program +## Solidity Target Chains -This template includes an example of how to call and transact with your program in Rust using [ethers-rs](https://github.com/gakonst/ethers-rs) under the `examples/counter.rs`. However, your programs are also Ethereum ABI equivalent if using the Stylus SDK. **They can be called and transacted with using any other Ethereum tooling.** +[This](https://docs.pyth.network/documentation/pythnet-price-feeds/evm#networks) document contains list of the EVM networks that Pyth is available on. -By using the program address from your deployment step above, and your wallet, you can attempt to call the counter program and increase its value in storage: +You can find a list of available price feeds [here](https://pyth.network/developers/price-feed-ids/). -```rs -abigen!( - Counter, - r#"[ - function number() external view returns (uint256) - function setNumber(uint256 number) external - function increment() external - ]"# -); -let counter = Counter::new(address, client); -let num = counter.number().call().await; -println!("Counter number value = {:?}", num); +## Mocking Pyth -let _ = counter.increment().send().await?.await?; -println!("Successfully incremented counter via a tx"); +[MockPyth](./MockPyth.sol) is a mock contract that you can use and deploy locally to mock Pyth contract behaviour. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds (the struct defined in [PythStructs](./PythStructs.sol)) as its argument. You can create encoded price feeds either by using web3.js or ethers ABI utilities or calling `createPriceFeedUpdateData` function in the mock contract. -let num = counter.number().call().await; -println!("New counter number value = {:?}", num); -``` +## Development -Before running, set the following env vars or place them in a `.env` file (see: [.env.example](./.env.example)) in this project: +### ABIs -``` -RPC_URL=https://sepolia-rollup.arbitrum.io/rpc -STYLUS_CONTRACT_ADDRESS= -PRIV_KEY_PATH= -``` - -Next, run: - -``` -cargo run --example counter --target= -``` - -Where you can find `YOUR_ARCHITECTURE` by running `rustc -vV | grep host`. For M1 Apple computers, for example, this is `aarch64-apple-darwin` and for most Linux x86 it is `x86_64-unknown-linux-gnu` - -## Build Options - -By default, the cargo stylus tool will build your project for WASM using sensible optimizations, but you can control how this gets compiled by seeing the full README for [cargo stylus](https://github.com/OffchainLabs/cargo-stylus). If you wish to optimize the size of your compiled WASM, see the different options available [here](https://github.com/OffchainLabs/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md). - -## Peeking Under the Hood - -The [stylus-sdk](https://github.com/OffchainLabs/stylus-sdk-rs) contains many features for writing Stylus programs in Rust. It also provides helpful macros to make the experience for Solidity developers easier. These macros expand your code into pure Rust code that can then be compiled to WASM. If you want to see what the `stylus-hello-world` boilerplate expands into, you can use `cargo expand` to see the pure Rust code that will be deployed onchain. - -First, run `cargo install cargo-expand` if you don't have the subcommand already, then: - -``` -cargo expand --all-features --release --target= -``` +When making changes to a contract interface, please make sure to update the ABI files too. You can update it using `npm run generate-abi` and it will update the ABI files in [abis](./abis) directory. If you create a new contract, you also need to add the contract name in [the ABI generation script](./scripts/generateAbi.js#L5) so the script can create the ABI file for the new contract as well. -Where you can find `YOUR_ARCHITECTURE` by running `rustc -vV | grep host`. For M1 Apple computers, for example, this is `aarch64-apple-darwin`. +### Releases -## License +We use [Semantic Versioning](https://semver.org/) for our releases. In order to release a new version of this package and publish it to npm, follow these steps: -This project is fully open source, including an Apache-2.0 or MIT license at your choosing under your own copyright. +1. Run `npm version --no-git-tag-version`. This command will update the version of the package. Then push your changes to github. +2. Once your change is merged into `main`, create a release with tag `v` like `v1.5.2`, and a github action will automatically publish the new version of this package to npm. diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml new file mode 100644 index 0000000000..94c6748454 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "contracts" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/examples/counter.rs b/target_chains/ethereum/sdk/stylus/examples/counter.rs deleted file mode 100644 index 9414df2053..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/counter.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Example on how to interact with a deployed `stylus-hello-world` contract using defaults. -//! This example uses ethers-rs to instantiate the contract using a Solidity ABI. -//! Then, it attempts to check the current counter value, increment it via a tx, -//! and check the value again. The deployed contract is fully written in Rust and compiled to WASM -//! but with Stylus, it is accessible just as a normal Solidity smart contract is via an ABI. - -use ethers::{ - middleware::SignerMiddleware, - prelude::abigen, - providers::{Http, Middleware, Provider}, - signers::{LocalWallet, Signer}, - types::Address, -}; -use dotenv::dotenv; -use eyre::eyre; -use std::io::{BufRead, BufReader}; -use std::str::FromStr; -use std::sync::Arc; - -/// Your private key file path. -const PRIV_KEY_PATH: &str = "PRIV_KEY_PATH"; - -/// Stylus RPC endpoint url. -const RPC_URL: &str = "RPC_URL"; - -/// Deployed pragram address. -const STYLUS_CONTRACT_ADDRESS: &str = "STYLUS_CONTRACT_ADDRESS"; - -#[tokio::main] -async fn main() -> eyre::Result<()> { - dotenv().ok(); - let priv_key_path = - std::env::var(PRIV_KEY_PATH).map_err(|_| eyre!("No {} env var set", PRIV_KEY_PATH))?; - let rpc_url = std::env::var(RPC_URL).map_err(|_| eyre!("No {} env var set", RPC_URL))?; - let contract_address = std::env::var(STYLUS_CONTRACT_ADDRESS) - .map_err(|_| eyre!("No {} env var set", STYLUS_CONTRACT_ADDRESS))?; - abigen!( - Counter, - r#"[ - function number() external view returns (uint256) - function setNumber(uint256 number) external - function increment() external - ]"# - ); - - let provider = Provider::::try_from(rpc_url)?; - let address: Address = contract_address.parse()?; - - let privkey = read_secret_from_file(&priv_key_path)?; - let wallet = LocalWallet::from_str(&privkey)?; - let chain_id = provider.get_chainid().await?.as_u64(); - let client = Arc::new(SignerMiddleware::new( - provider, - wallet.clone().with_chain_id(chain_id), - )); - - let counter = Counter::new(address, client); - let num = counter.number().call().await; - println!("Counter number value = {:?}", num); - - let pending = counter.increment(); - if let Some(receipt) = pending.send().await?.await? { - println!("Receipt = {:?}", receipt); - } - println!("Successfully incremented counter via a tx"); - - let num = counter.number().call().await; - println!("New counter number value = {:?}", num); - Ok(()) -} - -fn read_secret_from_file(fpath: &str) -> eyre::Result { - let f = std::fs::File::open(fpath)?; - let mut buf_reader = BufReader::new(f); - let mut secret = String::new(); - buf_reader.read_line(&mut secret)?; - Ok(secret.trim().to_string()) -} diff --git a/target_chains/ethereum/sdk/stylus/header.png b/target_chains/ethereum/sdk/stylus/header.png deleted file mode 100644 index dd12f4675db336da39b0f36ce92b9e9876eead50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 335346 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 z zk#m1~>3R4F7mZdB-k0y@p5wQlb3 z545Pox!!6M^U5)Z#y2<;KckuwCb+O)ZitceS72Khs`^M65%g;Cptse{SXe|W?Q~Pj zpMb}#m{_f?3dVc-OQlmwozF*!T zT*37J{TziTAP8OyTw24afJ=&l1}}E6Gwc6taRv~q2LUEng8m3GG?mj_&Tot#q{(;z zx)3AD6gyufS-m_}^@T**WFdPkT9oee<=&2jvPrEwXppjp^py209?k4LlE-omi`o@@7^g|w;iG?wscr|2aHtsMY!2^O z;1s+FUN^;1zsGgrwBI=+jz~T8>g_G_3g6l})uh9CR^i`0tobWemsHtWPYDMUc0XDj zu7pOI2NVS2mxl)CTJJ&^?%Wi(H%&%QY$h6lK^K&^|HraJ+#fuSo$WlQq}XQnw~MKb z_o-XHVAniEY9vQMv^P)?;3Vc)6m8u=YDKoN#*BmY2-djZGkFq^k9oDrWXC33Lo!o< zop0(-{7{RI&3nW^9V786233Peul^bnBID50*U~dx+v<591q(;Ayr|9mB5X+wtemt$ zk>fT-=DTVtU988J@iFZmEoVTaH7DxUy+|B%RPQegdcF0q8TBh*0r|o{!6oO{BxHnfJP9>Z~3t+p#@XVv{9a zJy&(ViY(naV}@u2Y>Ll5R&(!fk{jl%Z>BEO!OvEm>4hGEd`I8wk=g)aMD%I@=%~|1 zm94~PQQ;FXqt-CGq)800f+lidxQBJrp}0xvQ|Z)A+=A^;!#@y`X6XPY$n~z(0dUU3 zmG671KN>ZhER!`v+hqy*xc=vxx#HYuSyP8Km750*+Y9Y|pchRRU15kP7-Fxo=_dA& z(maXFwISE38dZ*Ij8e&@(EYak`BdQ9+*;M*76=QL_p36TWML<6_fc&17c7vH$#9IQfzAFJiQ_{`oI z5s1`sELaU4bY z>dfi%wnkyVA>U$yUOcOK&@*>n)pM_M5`|84f$_h zFu{Xtdr5Zoo-o4U{3#-OBE~onxzGx~R1zQZefqD@qmoyi>=sL(w{?U3xP(2Z5ZW|? z107UJ#(jGbn|Dt_1AHHidYH1*_fXkOxE^PxQD|~b{-@`(&c0~@Si>X1%i~YA^Fp~> z|6KKM+ErvOV{5)?f7rYbCSPjy5TLx4OB>z%V$vG^-jiQ`Cd|Qh)+H*RK2oK1L*DxY zQBFH=P>==RrAIibs+!WIR@OT3^+S(={pq~UkkLe`C3F@L0LYpboe&U}ZLL!HX$PL`X`5cm3sA z)tX~RC+icpqa0y`FgU#B`fhYX?=8l@K*42{;03#gjNz4IkVFHb7$GCx@h zLP~EVnML7q-)~5KZ}172UGL8}T0OY@@FOOMjcG%hDq2nlbYd7cV$;f|fS0q+eHBg5{o%+c?R4^sbLy7Y6-hx%}yTy5-v#J0E{CQyfHM zfRF1iwP{WNrUjHdU=-8w@rc;KGMaVY32GsF`vQQq2g%G%I%0k+bv}$F(bClna?$gt z4oG5^e=sxuoe}>2w6NFF;&HIZj$r@H_6$9RCk}(!$qwH!)K2>=e_~Ed|jEoW{ zc=9g25p8fl6RzLl5wl39wC^>t^bLIrMJWDf_bALAQ0T3*m1NHX@N__7>+LU82R8 zCD7E{`Q82;mVXklMU`f^c`>zr?vs&5m2+fUxjG9i!P3y+xv5^!MvqD{;ieSTRTcX} zFE1*mD)DHf%P-86%WaA%Cr^pK&^%U1ku8K_{}wZBQS0$Al;(MCC0BONQOxb%`HFt& z8@D>=!&fI`Mfz85-8*fc0?#_IzLAp#$|#h^h45&Em16Jj(r96?6_6)A4|Ni?il4xQR3VK3oVr7*WikXxGl^R+argox zZ|N#eMP(ib~}=$R3Yg-AGmdUwq-YEJYDzLSa$7^#6fUA#|pIZ6$UmmnL&9XR-MN%|~=3_!iU~VBJg~FkS2?CsV_s`8g zAb;8;O%<9L-_tu5-fw>Ql4PS{`P&j6Gw79nzB!C zS{G|zJ3glOGEM+)wyGUkvN=&^YuN5oM&U+ma^WeX?#LWrl)w7D`EU*NX2 zbG2Ul>x8ymMQc?6oK(+&vG=Lx!oBYLc@@Mbmj_I%tYsn#Da+{%lAGWDgUygTq|!5( zpm?)IpR&526EDQcwt+o0n^2bE{o|RGmnVXze%BpCc`c*)X9iiKNT%CJ{`^ zzhrh(WUze};rD))co+2=d}Bvk!zzwuA#<9qQckIV4p0c@#|t5yTP$86U;zq$`<)M*yW+DZ&rGt^o=Gaa4xKq2|KRgyP;TPeM8$d5 zpCf+Xc%M`KIme;YdF&;vuYRcptmEYZNyY-$-(>8hPgTWY_4&MDDVXILmnaX)XU+1G z)X&-9*jf5936gO5cW%{9#u{CG7Y0i9SUr~3!^;z$J=SaH>vooTHqLq?T0KPcX>rrv zCH1!&WzIj!;~wyolgQHf84rZOKPyjz&IEHRst#2JWPy@626j}&gj?m)pWsn@ljVPQ?5D$a0F-Y*Ki?zG%(0XpQBww^+;kw`VoE!D$yiM%)ck`9&SvPTcOvfX5SHbZx z^0(LZk@ct9tvukurVgh*Moi`-i#PK{U*@e}-;U^YpD{r;u7z1^@ER@M-Uf!kWtF|j zF`TVf2(G|{fcaHqWtmP^v;^>P~=SILV8dEJFlQS@2u{@^3 zg7Uyk;bRYMUahGw4$8*2o#nVPCYZ?-f^jf_=vuR_$YfsZM_wibs&LMqyD>zexKeeV zv+Es_+2qXe=aAob_jKItW|7-HRQrG?xc8P>8U6B97BdoA9l*JU{G4Nxsz0+7Hw-+F z6RHTb@59UAyH*c|k~7!Sn_cfl9cTxplz&GEwAwD7wkGQSzQ zCfFa{yx0p+pXw*ggibw>g`vN6QV7|H(yfx+0!uGF1-N*tIrP@ReO3*JrRHy{*MUZf z9Z1@J{n6Bn>~^FMDU5Ij_I2W*_=F%2THJ)ddHw*4j6sE7DVYByP)@2F3V~fDoSxf; z2X1L7r^`HWXpzP@>vGnEy3qYPpQSP1%jZCCIhAMOTUW zUj!wBMZ)tx7U0j9EP^)fsr@N)QTSPlg6p-p&M{TI>Q(El-KS$fDI896i)WN95a0)U zkZ{ItCAZ)>>xbQKE1^Yu;Vex+bpxkI9%j5M>qg*^47&pKx2ngFp+17kt0P|hI%^8< zwyy;R&nP!Ij)jMZQR`%?^BOm8QT*kJVpj}*+}7@k9QetQX{pKZ_RR`5@_y5v>JjQ1 z?D!Ee>l;9X%TO|~Pk`4Z!ME?^HyXp@0UT7PD(;h-e=MGIdid}bD^TLauTh)_4Ob<` zty$`4%Ws4+5uPmzxb`77e5mD!ENKIKpX-7mMh9tN9IY&0D2#VyO2MLDirt6 zI|gT@#|fBby{F%`)Vo1qkIObhLRQyKN-Ka(vh>|nLI|~%ce0)7Ntl)uV#APVH_nC? z2e(9GL#g?|6u!Nc#+XpkrtpzHLv1KK_s(zoKXGxYL+Luz-si=3zm<#&op53tjpC?p zndf5;$}y=jcVyoox&_q$3Nd5aj~M&FIIVfH%Y5H=P$PGSHRq|*CM4`q8^#jTsTPTTYw`jK4ubi<^<35XF!zP0Q zuG8T6N`jtIq=$P;ffz0#fP|N1rSe5`bm14*g_rQ7RSPO%DX@05NaZ;x2=XYnuA0UC2cFJU3T#3~;-A$&FmcE-5}Az>Tve3`{)j$radkxz z^y&P{{FV>}7 z#5C;$z%J0CpM4$RW><4jXp(nlF$AAa7{NKk(~krOG~T@8R)bG9gpNNoOVcc)9`0&H zH&aN<3|;e(r7YQ^%%RS{v*Aic$T0pB{wKfhPcHyDm=c=Z+Nzt$2w8W?NVj|ql7M)7 zzmy4JXTLzo>lPt$Eha}$S%|wZJkk8KCwJ@59dtEj6=UTt1<7T1c=5c5?1%4F7 zWaH<=IiZwxOVTbFZs&V|MpQ}-TqdpfwKcsm`SVaV!ZR~LR#Bt$hM2`lb+J4!KGImi z2hbC=2>5sX@^s!caa8?dg4gW@uFp7n_EDiC{DR>DFW(T|Vat!EF-H(|!TY-`VYF`+ z;s?R6)mp_93^r8Y2kUf(8NtA)>i^fAy3q6C=2gug5&xzPYG>R{Kc83Pq;{KusVRkt;cQDG7F{kt>!@#NC( zyV9Ql`~pw$gNlEBANBmvN1N*^p8C3IWK)`sSifkyl!%*ffy}4wILYvVIUf!2RI%+C zVocbFU|M|QkiZP7g8!xwLFBP%1@5_?EPmWBQ!;3#t7Wh8dL z`s^_MB<|A7eq@wZ$%|LuOW~ayP*(`%R0W~q9ymvDO=mL$Wqqz| zK%LS912Y83w*ZJ>5s<^9Z6M1~X~jqcH=@g35w9JClcIDxi;#gVCQGNx28P2JYxp8m4V_9&9|4>L)?V|3VV`(poHG%Cb zOZ#cAj70z7{>-)Y9|OPB+{9Rg7@RBVy_3*N$s@T^YLJe+G9etWY;eRI6yd_M|o6L&S#1MNxU#o zmfArA+Eu~g^%?7lfe16A22xS6B?JMNLG;7*S^JLHU6PdGa!>#N0}_#>7Jud62EL(0 zK2r1iXd7`WciAf=K#rUYAqh7@5R1E7M`204MxVden_2;f)l>&LX5$f=Mh z61%i>gSVs1la?$7p#tD95NA4rCBDIP?tGPhg5USgsf!KO8f@_{7cVnmIsszxoKT>Q zPaXm7#c!|bkB5j&-IYP_Wt6wFauU{cf3$Dc8VGyC5m!QpO!teICTnLbd~_u_bArXN zf9`{kpj{>%j5l7g_GdL3D)1~T8PcARfr|XgsBEY9AtC^-AmPMsGoH$QJ9)3GstVCu z*ZXvcN)a(d0-Axn<&a(@teQ-k0|=rmA}{|b$i!ulxOMl=8ryy-OBpBapK8+VO3b@l zlh2u4+LP*uE4r?i8c)m5>UqA#mY0&S#2WY48|XRhZOv2w)_Csv@8`vX0+>ORads%DA{SaZuX5-lCIj$_i-n=p0R%MS zWIEzJ`c2LisaZ)h%>A1aw2No&B9+iSHJ?vXK_ITiU>v(-5yM*vLXmyP3AgOEk>Ex* zg<^n|Lolf^`BP59pEjtUO2G$iFTw4X>Yf|7XcDl_eB;(}en<673Y0zd@sV%+HlJ&2 zYfuBK=`^vVG|T(6zOJgHmd?Gog=1NLVcAax^C9``#Z9Fbf{o5)YoXxtg9GA9u-~ky zsdBez@znfY7WfN@sJ;K#vrl07*6h5U=;3=u2=Jt+YL>uQiiGb+4sajbCU$j_Sqn4F zmOlsuL(Qn5g$45HCMqXHLviaORR78>?|bKn`Az1pi_~I_GQpp?dHUMlOpJ{xLxn%M zevlOBjq|jPb40}Hbvg#d^=Y5JO@PRSiM0ZM&i$$tBb!-K zN1DB5)U9>m{#3TvWV7#y+Mo7;hCNBdl^X1jf5NTlnA337c9zJxbR2tFrQ1D^72*EW z<1|k|)`5kMu^&)4gPcwF%w6Ta>G3|7&;P}pAL@x+7xo(RGjRKUd$783F25b+^Y95{ z+bQ=A!u(?RQ>_9wrwIjfzmeFzM<%QrS67e&uthL^<{QGv`oUW^bUDra3Eu~nrSA@G%Ll_-^BMXNW zcbD7;^Upul3q*e|N+wvw4BB>YMtpCOsFFX+YOOjMs{brw^ji2!Yd@$fVf$4}sAHV> zM$;Ld72%`w2^*k!XPHYI`U~RcmaXBKd>%f^(+CyTZvvbnL>xU^rd??aU?gp{-%R62 z!xhY~S>V}}cf1m^K=Q&aH?sOYv3=;FNFm)z*VN?dJy;PFSV9c_$?Da1XWmlf9utWQ zi5i8_Hpww2kaMp_ABL$f4zPHvYIF=fJ|R&I5YHAMw#V;QV*YjubS^HGuwB zj}NhV0nO!eqAcWlXe6mw@AEx(mE8#0P4mpq)wR*?$QRDD1Nv1^vdhj;^+n{Xs^<9Z z_CHVWzOwesk5JcGWr6@@)yd2$nDoA%T?o!tAA#7Du&k^*glw-q(zNDaHA(8#%$+2; z72mynICn`ayxnlr#n456)_5hC8V9CJ<&Qh*hxMD#aSazaPjD}x z^cSP|g0v1G!k^OGN%FJfmd2lZ@uhxTtke?A1xzhX7lInu%^mZjAJ`{zcV!0DAlH=i zLQQZqf{7*qh3kxh3*KyLgvm5OTnO2HK?rHUPOPWQ?5 ziC^gOt2r~rBMm!F8>PC;MD(d6G+_u@6YOy^{RI0qCR&*quE5N}5ul!btOkJ!G- z=(MB83S$mJO%Zv!y3%+60eMi3&Wop{y~T_(&*_3<0#P>CR5+lXb{|G?%x1?1fDTW- z!hQ-xNUNnqurQ(-i7A%JlyK{gK-gKLMBtE|*tMnWV4`+8*+XuNt_=4@dEq}p9&2UE z{5Iu$T=@hkcX#$$74Vr=i&irVoM=j&RNBl?)MCjGmBR>u=04E2&ZMH_P|IpJ$8~~Q z*xSzoI>+2y>;bhadki+9$%(=viZ%2Ac4Xprj=Q)a$KwI}mli`|u{DkMvAG?1PLTXY zLq_f@U=3OanzQZkCxwXBM>{nl)i!f~849AD;N}U;0Xp*t;T`F?SMrJs=APi#KEP8$ zrrhkavpT$%WAE?VyKRpF7#AB5sn~(9`Qx&HJVB`arexqy$<4rS=~mNG9*GHY(Xp@3 z^%ly@_mF>(p!OKYOSN>Pk9nFr2Llu*S7MOlpY3QEplaWuU)e^Eua0QKrSZE}7&!Kn z@ZZHTWE~8qzK7k_{aJJ%{a~8xZR42Soy*ZEVUbsP^dqQ77~`w$egRr{|HvZ}1%{wU zXb;M(o)FDg0(kq&mop#P_x4Au1KJgcIB?$3A$ z^u0&({*$Mi?B@qLc(?dkUU~d*g&PSaW8_zUj4)!w^zU)tWp@f!2xYP@lwO+v$ZRK- z!8H@&t-EZsl`g09r7godoiWND`;iB{gO}|@)o;P*uG8xyN~@RXHg|0w8RvXKiY z$qANIA9D+>=5yd;h<@WzKO2ujl@rwDcY+K>U4w4ysVaZ7L2T^A?i(dqOzcMrc#ZI&3=(;Zl z3z0Lrjgi(!mEE~VCE;JCHhKZ`)8(%jnq|ifxWB0x|14;EdyaQ-T%YmS?2`Vhzm&pg z`R{Xor!T!EijVHy|4ENuoBG7v9%ch+mWqAf8a^f7qo%{eO`JZYipa5)Va#WO*W3dH z7<}C6c~B0wQ3St?(_N{3dI=gySJy79J+@42;#wuoG}8|pMA?8SiF$AThIQjBXFqJfYB5x zaPg)x?+O4e&W;d6Y5~qkCz|VJfyJ*1Rzn~QwFzhx6orH2h7$0N=k78mWw|f%%9m4< zWdXj0t@pL|o-|se4-Op|?m&=HHkn&VuZ0$p59X4{85|WIwr51kg^E8Ow#%fmJ!E48 z{so`H7BJSg?u;Up3#tbyw4NCTk}|JBS;Mx0MRYu!TgHjvg|a(J8VrfWu%ot1i=TOxfDM28dX(rkm=1Gv}Q=cWtC-}L*+5|?@t^l_ah{K55 zlc-s*%1}x&FhO&jY?p~AiKvOW46m}n^rYY5l>`1rL}{E?tXiXfvVCHMiI5!1kvXP> z)?0nI#6w1rBq2kK9n%x0KEZL$#2ir0krAstj0a#8a4+?w%GR>QOU8N^jrZOfYfMC} z`Z8NMa@u`kGajAnAQUhe*+*Tqi|4a*&ZrsD=`Z>nr!0HaDa&KR$4rOugn00j9@J)l zOw;$Og+Q0!oCqJeM@{MEh}Q4#U~$4Z$utsK)Bq=Pk02rB73+F+2FTQWy}?}Ou3_^A zTL)3q!^6_AY2bsGi)`&T&?1vl`+0S-#JfZ5NcDi$Ha?d@rUb=%xWDF`FvKJ+gI@*4 zsrEC@o@SU7da+tu=eg&yhRR(f`B4yY`zIzKA`_Dgk zT~)Z$2T*a1-7xQY??(jK9SbVLY^-Nh?t7o^_^!d(dw~nb?^k_Z%WgLKY}-xLVRe?7mCBqUb3nkXs|GM4^J|Q(!O(B9oV)W3n4!XyM$;0^e~PS&UL1+v z+wJjHg1(x9vdg5e2bZ}uY5_M_XOD1pVcja__1(p=DL@2STLu(4w%cUG6neq?<(~oT zE(h>V)FEc_X@9MQzGe06XOH8w8q~v*Sr6rz0*O@R#rBVf)!sEZK|%0psagwz=<@Tq zAe;tl`2srM^2~reQJkf#nai0-6Z6u7;j)uuQVP?N4 z8hc+)ov$@lC7}WKk5!CpfZ5s_n3u}lF;=BnlRO7hi#7UZ6|CP`cUL)76cgd~@Fh45 zgellGHlb=n+*G*rqt!KTIpEv}|9L164)80&pItPPd2Qk6#_W6!~yYE~E;L)=G;ts<^_Kx^(U?XM7DC@bE z+cB_7{;;$UXXpD{nW9yd;<9M?x}7A%w2Cl32_>ixmI-stDAW|Cu4ypJ9UH*?>(%|k z?2-y1RA3Bk(N*xCp{r^myP^G(tPmIn@`Hc)rP=Qp%1W^fcAMJ=lL};r$2dd4zZ+s$ zaSYa&y7y5tbpJuHp%*rJpEA+#Jz|I4C%y*|mk07FQGoKtm zp?}emU?#Ypbh0E`?h+D9-WOuWZmOFeJbt14{gcwYfe~=}dp3=}RJVuK-PW14 z3s;XLWK8EtI7?NUx7UC}G$MEl`j9nJ|11OHIdw1+n~-HEfb_h%_qC`L-hghvUDW;4 z=KnL}+IvEg|r=I4kM+f=yl=fz2QW8$xSCn^$mC18!8n~M=(^>cAQ$#oGrU=-pyF| zNy7=UOj*CMtYM%=Q`%%V5_#SPgy-06tlo_w`O#Xg+{zp=(Xnt{v(;ur@h2Cg{l5YrhU4a^zdJ5gd|;iNRK; z!K{-Jt^IL#GT=e+$-ELc%JySlD*i=*8{Dp$HcWf(*wZCv`ry!(xEz@tR)(vS!u(u; zP8^|*p~(A4fN|dL0L6SRERfp??gMJg7}c2rTL4=uG%Gr0^X&QMXsXX#kKg_F#8`Wm zOgke<2@YO=)3U!zoT4K{O8b$nC{RNG0)=cxK{?j<^mJMWd)F^QHKWXOrPsnN=fYt7 zDaxj9U+4#q%cZ`hg;Qqc^j^9IEV&H}Ai};@YD9s?^#<;^m5YzTP_YxyfZ-$E{psC zQ)as_AC%pt(ck4R#C6VGobTEAs4(qTxEqhu?h#pSYWd$8#r-WSxEa*{?q4uYwNfB- z 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 U256 { - self.number.get() - } - - /// Sets a number in storage to a user-specified value. - pub fn set_number(&mut self, new_number: U256) { - self.number.set(new_number); - } - - /// Sets a number in storage to a user-specified value. - pub fn mul_number(&mut self, new_number: U256) { - self.number.set(new_number * self.number.get()); - } - - /// Sets a number in storage to a user-specified value. - pub fn add_number(&mut self, new_number: U256) { - self.number.set(new_number + self.number.get()); - } - - /// Increments `number` and updates its value in storage. - pub fn increment(&mut self) { - let number = self.number.get(); - self.set_number(number + U256::from(1)); - } -} diff --git a/target_chains/ethereum/sdk/stylus/src/main.rs b/target_chains/ethereum/sdk/stylus/src/main.rs deleted file mode 100644 index 3bbccf9cb5..0000000000 --- a/target_chains/ethereum/sdk/stylus/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![cfg_attr(not(feature = "export-abi"), no_main)] - -#[cfg(feature = "export-abi")] -fn main() { - stylus_hello_world::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;"); -} From 483eced0a91ed9d100fe40ea51a3ac01434db700 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 18 Sep 2024 19:37:03 +0000 Subject: [PATCH 004/160] chore: docs setup --- target_chains/ethereum/sdk/stylus/.gitignore | 9 + .../ethereum/sdk/stylus/docs/antora.yml | 6 + .../sdk/stylus/docs/modules/ROOT/nav.adoc | 2 + .../stylus/docs/modules/ROOT/pages/index.adoc | 9 + .../sdk/stylus/docs/package-lock.json | 746 ++++++++++++++++++ .../ethereum/sdk/stylus/docs/package.json | 15 + 6 files changed, 787 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/docs/antora.yml create mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc create mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc create mode 100644 target_chains/ethereum/sdk/stylus/docs/package-lock.json create mode 100644 target_chains/ethereum/sdk/stylus/docs/package.json diff --git a/target_chains/ethereum/sdk/stylus/.gitignore b/target_chains/ethereum/sdk/stylus/.gitignore index fedaa2b1d2..27b33784a5 100644 --- a/target_chains/ethereum/sdk/stylus/.gitignore +++ b/target_chains/ethereum/sdk/stylus/.gitignore @@ -1,2 +1,11 @@ /target + .env + +**/node_modules/ + +docs/build/ + +**/.DS_Store + +**/nitro-testnode \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/docs/antora.yml b/target_chains/ethereum/sdk/stylus/docs/antora.yml new file mode 100644 index 0000000000..524da2ac96 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/antora.yml @@ -0,0 +1,6 @@ +name: contracts-stylus +title: Contracts for Stylus +version: 0.1.0 +prerelease: '-rc' #we can remove this with the official release +nav: + - modules/ROOT/nav.adoc diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000000..fe72c727c6 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc @@ -0,0 +1,2 @@ +* xref:index.adoc[Overview] +* xref:deploy.adoc[Deploying Contracts] diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 0000000000..01bf3f8763 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,9 @@ +:stylus: https://docs.arbitrum.io/stylus/stylus-gentle-introduction[Stylus] + += Contracts for Stylus + +*A library for secure smart contract development* written in Rust for {stylus}. + +WARNING: This repo contains highly experimental code. It has never been audited nor thoroughly reviewed for security vulnerabilities. *Use at your own risk.* + +NOTE: You can track our roadmap in our https://github.com/orgs/OpenZeppelin/projects/35[Github Project]. diff --git a/target_chains/ethereum/sdk/stylus/docs/package-lock.json b/target_chains/ethereum/sdk/stylus/docs/package-lock.json new file mode 100644 index 0000000000..c57c4419d2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/package-lock.json @@ -0,0 +1,746 @@ +{ + "name": "docs", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "0.0.1", + "license": "ISC", + "devDependencies": { + "@openzeppelin/docs-utils": "github:OpenZeppelin/docs-utils" + } + }, + "node_modules/@frangio/servbot": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", + "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", + "dev": true, + "engines": { + "node": ">=12.x", + "pnpm": "7.5.1" + } + }, + "node_modules/@openzeppelin/docs-utils": { + "version": "0.1.5", + "resolved": "git+ssh://git@github.com/OpenZeppelin/docs-utils.git#971e6517ac8654231c2b11a15850f8477bc8e42b", + "dev": true, + "license": "MIT", + "dependencies": { + "@frangio/servbot": "^0.2.5", + "chalk": "^3.0.0", + "chokidar": "^3.5.3", + "env-paths": "^2.2.0", + "find-up": "^4.1.0", + "is-port-reachable": "^3.0.0", + "js-yaml": "^3.13.1", + "lodash.startcase": "^4.4.0", + "minimist": "^1.2.0" + }, + "bin": { + "oz-docs": "oz-docs.js" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-port-reachable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", + "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + } + }, + "dependencies": { + "@frangio/servbot": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", + "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", + "dev": true + }, + "@openzeppelin/docs-utils": { + "version": "git+ssh://git@github.com/OpenZeppelin/docs-utils.git#971e6517ac8654231c2b11a15850f8477bc8e42b", + "dev": true, + "from": "@openzeppelin/docs-utils@github:OpenZeppelin/docs-utils", + "requires": { + "@frangio/servbot": "^0.2.5", + "chalk": "^3.0.0", + "chokidar": "^3.5.3", + "env-paths": "^2.2.0", + "find-up": "^4.1.0", + "is-port-reachable": "^3.0.0", + "js-yaml": "^3.13.1", + "lodash.startcase": "^4.4.0", + "minimist": "^1.2.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-port-reachable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", + "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } +} diff --git a/target_chains/ethereum/sdk/stylus/docs/package.json b/target_chains/ethereum/sdk/stylus/docs/package.json new file mode 100644 index 0000000000..ac29e6bf72 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/package.json @@ -0,0 +1,15 @@ +{ + "name": "docs", + "version": "0.1.0", + "scripts": { + "docs": "oz-docs -c .", + "docs:watch": "npm run docs watch", + "prepare-docs": "" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@openzeppelin/docs-utils": "^0.1.2" + } +} From 9d4a36862cadd8d06d6cb44ebaf610421533043d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 19 Sep 2024 23:23:49 +0000 Subject: [PATCH 005/160] chore: pyth contracts init --- .../ethereum/sdk/stylus/contracts/Cargo.toml | 15 +++ .../sdk/stylus/contracts/src/abstract_pyth.rs | 5 + .../sdk/stylus/contracts/src/errors.rs | 116 ++++++++++++++++++ .../sdk/stylus/contracts/src/ipyth.rs | 36 ++++++ .../ethereum/sdk/stylus/contracts/src/lib.rs | 16 +++ .../sdk/stylus/contracts/src/mock_pyth.rs | 0 .../sdk/stylus/contracts/src/structs.rs | 38 ++++++ 7 files changed, 226 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/errors.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/structs.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index 94c6748454..6b86c607e3 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -8,6 +8,21 @@ version.workspace = true edition.workspace = true [dependencies] +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true +stylus-proc.workspace = true + + +[features] +# Enables using the standard library. This is not included in the default +# features, because this crate is meant to be used in a `no_std` environment. +# Currently, the std feature is only used for testing purposes. +std = [] + +[lib] +crate-type = ["lib", "cdylib"] [lints] workspace = true diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs new file mode 100644 index 0000000000..f8253d2b24 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs @@ -0,0 +1,5 @@ +use crate::ipyth::IPyth; + +impl AbstractPyth for IPyth { + +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs new file mode 100644 index 0000000000..b2f38e4a33 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs @@ -0,0 +1,116 @@ +use alloy_sol_types::sol; +use stylus_proc::SolidityError; +use stylus_sdk::call::MethodError; + +sol! { + // Function arguments are invalid (e.g., the arguments lengths mismatch) + // Signature: 0xa9cb9e0d + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidArgument(); + + // Update data is coming from an invalid data source. + // Signature: 0xe60dce71 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidUpdateDataSource(); + + // Update data is invalid (e.g., deserialization error) + // Signature: 0xe69ffece + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidUpdateData(); + + // Insufficient fee is paid to the method. + // Signature: 0x025dbdd4 + #[derive(Debug)] + #[allow(missing_docs)] + error InsufficientFee(); + + // There is no fresh update, whereas expected fresh updates. + // Signature: 0xde2c57fa + #[derive(Debug)] + #[allow(missing_docs)] + error NoFreshUpdate(); + + // There is no price feed found within the given range or it does not exists. + // Signature: 0x45805f5d + #[derive(Debug)] + #[allow(missing_docs)] + error PriceFeedNotFoundWithinRange(); + + // Price feed not found or it is not pushed on-chain yet. + // Signature: 0x14aebe68 + #[derive(Debug)] + #[allow(missing_docs)] + error PriceFeedNotFound(); + + // Requested price is stale. + // Signature: 0x19abf40e + #[derive(Debug)] + #[allow(missing_docs)] + error StalePrice(); + + // Given message is not a valid Wormhole VAA. + // Signature: 0x2acbe915 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidWormholeVaa(); + + // Governance message is invalid (e.g., deserialization error). + // Signature: 0x97363b35 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceMessage(); + + // Governance message is not for this contract. + // Signature: 0x63daeb77 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceTarget(); + + // Governance message is coming from an invalid data source. + // Signature: 0x360f2d87 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceDataSource(); + + // Governance message is old. + // Signature: 0x88d1b847 + #[derive(Debug)] + #[allow(missing_docs)] + error OldGovernanceMessage(); + + // The wormhole address to set in SetWormholeAddress governance is invalid. + // Signature: 0x13d3ed82 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidWormholeAddressToSet(); +} + + +/// A Pausable error. +#[derive(SolidityError, Debug)] +pub enum IPythError { + InvalidArgument(InvalidArgument), + InvalidUpdateDataSource(InvalidUpdateDataSource), + InvalidUpdateData(InvalidUpdateData), + InsufficientFee(InsufficientFee), + NoFreshUpdate(NoFreshUpdate), + PriceFeedNotFoundWithinRange(PriceFeedNotFoundWithinRange), + PriceFeedNotFound(PriceFeedNotFound), + StalePrice(StalePrice), + InvalidWormholeVaa(InvalidWormholeVaa), + InvalidGovernanceMessage(InvalidGovernanceMessage), + InvalidGovernanceTarget(InvalidGovernanceTarget), + InvalidGovernanceDataSource(InvalidGovernanceDataSource), + OldGovernanceMessage(OldGovernanceMessage), + InvalidWormholeAddressToSet(InvalidWormholeAddressToSet) + +} + +impl MethodError for IPythError { + fn encode(self) -> alloc::vec::Vec { + self.into() + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs new file mode 100644 index 0000000000..636bc6d52d --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs @@ -0,0 +1,36 @@ +use alloc::vec::Vec; +use alloy_primitives::{U64, U8}; +use alloy_primitives::{Bytes, FixedBytes}; +use stylus_proc::{public,external, sol_interface}; +use crate::structs::{Price, PriceFeed}; + + +sol_interface! { + interface IPythMethod { + function updatePriceFeeds(bytes[] calldata updateData) external payable; + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + } +} + +pub trait IPyth { + type Error: Into>; + fn get_price_unsafe()->Result; + fn get_price_no_older_than(id:Vec>, age:U8) -> Result; + fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; + fn get_update_fee(&self, update_data: Vec) -> Result; + fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; + fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; +} + + + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index e69de29bb2..1e627d7d71 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -0,0 +1,16 @@ + + + +#![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![deny(rustdoc::broken_intra_doc_links)] +extern crate alloc; + +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; + +pub mod structs; +pub mod errors; +pub mod ipyth; +pub mod abstract_pyth; +pub mod mock_pyth; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs b/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs new file mode 100644 index 0000000000..a6743ef166 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs @@ -0,0 +1,38 @@ +use alloc::vec::Vec; +use alloy_primitives::{ I32, I64, U256}; +use stylus_proc::{sol_storage}; +use stylus_sdk::call::Error; + + +sol_storage! { + pub struct Price { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publish_time; + } + + pub struct PriceFeed { + // The price ID. + bytes32 id; + // Latest available price + Price price; + // Latest available exponentially-weighted moving average price + Price ema_price; + } +} + +impl Price { + + pub fn convert_to_uint(&self) -> Result { + if self.price.gt(&I64::ZERO) || self.expo.gt(&I32::ZERO) || self.expo.lt(&I32::MIN) { + return Err(Error::Revert(Vec::new())); + } + //let price_decimal = self.expo.as_u32(); + return Ok(U256::from(10000000000000000000u128 * self.price.as_u64() as u128)) + } +} \ No newline at end of file From 900f2d4e86771a1f2be294aff1bace8f327d09d6 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 19 Sep 2024 23:23:58 +0000 Subject: [PATCH 006/160] chore: docs command setup --- target_chains/ethereum/sdk/stylus/docs/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/docs/package-lock.json b/target_chains/ethereum/sdk/stylus/docs/package-lock.json index c57c4419d2..0a016da722 100644 --- a/target_chains/ethereum/sdk/stylus/docs/package-lock.json +++ b/target_chains/ethereum/sdk/stylus/docs/package-lock.json @@ -1,15 +1,15 @@ { "name": "docs", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "docs", - "version": "0.0.1", + "version": "0.1.0", "license": "ISC", "devDependencies": { - "@openzeppelin/docs-utils": "github:OpenZeppelin/docs-utils" + "@openzeppelin/docs-utils": "^0.1.2" } }, "node_modules/@frangio/servbot": { @@ -450,7 +450,7 @@ "@openzeppelin/docs-utils": { "version": "git+ssh://git@github.com/OpenZeppelin/docs-utils.git#971e6517ac8654231c2b11a15850f8477bc8e42b", "dev": true, - "from": "@openzeppelin/docs-utils@github:OpenZeppelin/docs-utils", + "from": "@openzeppelin/docs-utils@^0.1.2", "requires": { "@frangio/servbot": "^0.2.5", "chalk": "^3.0.0", From 583ca72f319397012e6e7a792cc8a60176071ea1 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 19 Sep 2024 23:24:05 +0000 Subject: [PATCH 007/160] mock: example project --- .../ethereum/sdk/stylus/example/Cargo.toml | 13 +++++++++++++ .../ethereum/sdk/stylus/example/src/main.rs | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/example/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/example/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/example/Cargo.toml b/target_chains/ethereum/sdk/stylus/example/Cargo.toml new file mode 100644 index 0000000000..3134bfdcbe --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/example/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/example/src/main.rs b/target_chains/ethereum/sdk/stylus/example/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/example/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From c813c7a877512c2c1450b1ddc8434f77ffaa8e40 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 19 Sep 2024 23:24:16 +0000 Subject: [PATCH 008/160] chore : created test workspace --- target_chains/ethereum/sdk/stylus/tests/Cargo.toml | 13 +++++++++++++ target_chains/ethereum/sdk/stylus/tests/src/main.rs | 3 +++ 2 files changed, 16 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/tests/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/tests/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/tests/Cargo.toml b/target_chains/ethereum/sdk/stylus/tests/Cargo.toml new file mode 100644 index 0000000000..62edb503cf --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/tests/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tests" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/tests/src/main.rs b/target_chains/ethereum/sdk/stylus/tests/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From c088e12a6d234b12bdea6138aa399fa681483f3b Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 19 Sep 2024 23:24:32 +0000 Subject: [PATCH 009/160] chore: changed toml version --- target_chains/ethereum/sdk/stylus/Cargo.lock | 738 +++++++++++++++++++ target_chains/ethereum/sdk/stylus/Cargo.toml | 45 +- 2 files changed, 774 insertions(+), 9 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/Cargo.lock diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock new file mode 100644 index 0000000000..bb66fe04c4 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -0,0 +1,738 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +dependencies = [ + "const-hex", + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "contracts" +version = "0.0.1" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "mini-alloc", + "stylus-proc", + "stylus-sdk", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.77", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "example" +version = "0.0.1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[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.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "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" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd02e91dffe7b73df84a861c992494d6b72054bc9a17fe73e147e34e9a64ef3" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26042693706e29fb7e3cf3d71c99534ac97fca98b6f81ba77ab658022ab2e210" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tests" +version = "0.0.1" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "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 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 062b6d627f..b6b506dea1 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "contracts", + "contracts", "example", "tests", ] default-members = [ "contracts", @@ -20,7 +20,7 @@ version = "0.0.1" edition = "2021" [workspace.lints.rust] -missing_docs = "error" +missing_docs = "warn" unreachable_pub = "warn" rust_2021_compatibility = { level = "warn", priority = -1 } @@ -29,15 +29,42 @@ pedantic = "warn" all = "warn" [workspace.dependencies] -alloy-primitives = "=0.7.6" -alloy-sol-types = "=0.7.6" +# stylus-related +stylus-sdk = { version = "=0.6.0", default-features = false } +stylus-proc = { version = "=0.6.0", default-features = false } mini-alloc = "0.4.2" -stylus-sdk = "0.6.0" -hex = "0.4.3" -dotenv = "0.15.0" -tokio = { version = "1.12.0", features = ["full"] } -ethers = "2.0" + +alloy = { version = "0.1.4", features = [ + "contract", + "network", + "providers", + "provider-http", + "rpc-client", + "rpc-types-eth", + "signer-local", + "getrandom", +] } +# Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need +# to keep both versions for compatibility with the Stylus SDK. Once they start +# using `alloy` we can remove these. +alloy-primitives = { version = "0.7.6", default-features = false } +alloy-sol-types = { version = "0.7.6", default-features = false } + +const-hex = { version = "1.11.1", default-features = false } eyre = "0.6.8" +keccak-const = "0.2.0" +koba = "0.2.0" +once_cell = "1.19.0" +rand = "0.8.5" +regex = "1.10.4" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } +tokio = { version = "1.12.0", features = ["full"] } + +# procedural macros +syn = { version = "2.0.58", features = ["full"] } +proc-macro2 = "1.0.79" +quote = "1.0.35" + [profile.release] codegen-units = 1 From 62030d7d375e27171b8abc34dd05e38ea4fa6c57 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 20 Sep 2024 00:31:57 +0000 Subject: [PATCH 010/160] chore: created mock pyth workspace --- .../ethereum/sdk/stylus/mock/Cargo.toml | 20 +++++++++++++++++++ .../ethereum/sdk/stylus/mock/src/lib.rs | 0 .../ethereum/sdk/stylus/mock/src/main.rs | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/mock/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/mock/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/mock/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml new file mode 100644 index 0000000000..80270b2646 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mock" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true +stylus-proc.workspace = true + + + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/mock/src/main.rs b/target_chains/ethereum/sdk/stylus/mock/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/mock/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From d731068304cca9b7251c0e14c79e4812cc5c1bad Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 23 Sep 2024 00:52:52 +0000 Subject: [PATCH 011/160] chore: renamed project sdk --- target_chains/ethereum/sdk/stylus/contracts/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index 6b86c607e3..0623bdf231 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "contracts" +name = "pyth-sdk" authors.workspace = true license.workspace = true keywords.workspace = true From 83ccc12a0265c53ec3410dd978bd280b7e78260d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 24 Sep 2024 22:56:33 +0000 Subject: [PATCH 012/160] chore: refactored files --- .../ethereum/sdk/stylus/contracts/src/mock.rs | 33 ++++ .../contracts/src/pyth/abstract_pyth.rs | 66 ++++++++ .../sdk/stylus/contracts/src/pyth/errors.rs | 117 +++++++++++++++ .../sdk/stylus/contracts/src/pyth/ipyth.rs | 18 +++ .../sdk/stylus/contracts/src/pyth/mod.rs | 7 + .../sdk/stylus/contracts/src/pyth/solidity.rs | 142 ++++++++++++++++++ .../sdk/stylus/contracts/src/pyth/structs.rs | 44 ++++++ 7 files changed, 427 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/mock.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs new file mode 100644 index 0000000000..c4844de889 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs @@ -0,0 +1,33 @@ +use stylus_sdk::{alloy_primitives::{U256, FixedBytes}, prelude::*}; +use crate::{ + errors::IPythError, + ipyth::{AbstractPyth, IPyth}, +}; +use stylus_proc::storage; +use stylus_sdk::{call::Error, storage::{StorageMap,StorageB32, StorageU256, StorageKey}}; + + + +sol_storage! { + #[entrypoint] + pub struct MockPyth { + mapping(bytes32 => PriceFeed) price_feeds; + uint single_update_fee_in_wei; + uint valid_time_period; + #[borrow] + AbstractPyth abstract_pyth; + } +} + +#[public] +#[inherit(AbstractPyth)] +impl MockPyth { + pub fn initialize(&mut self, valid_time_period:U256, single_update_fee_in_wei:U256) { + self.valid_time_period.set(valid_time_period); + self.single_update_fee_in_wei.set(single_update_fee_in_wei); + } + + // pub fn get_price_feed(&self, id:FixedBytes<32>) -> Result { + // return self::AbstractPyth.query_price_feed(id) + // } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs new file mode 100644 index 0000000000..0cb120123c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs @@ -0,0 +1,66 @@ +use stylus_proc::{public, sol_storage}; + +use crate::pyth::ipyth::IPyth; +use crate::pyth::errors::IPythError; +use crate::pyth::structs::{Price, PriceFeed}; +use alloc::vec::Vec; +use alloy_primitives::{Bytes, FixedBytes,U64, U8}; + +use crate::utils::helpers::call_helper; + +use super::solidity::{getPriceUnsafeCall}; + +sol_storage! { + pub struct AbstractPyth { + address _ipyth; + } +} + +#[public] +impl IPyth for AbstractPyth { + type Error = IPythError; + + fn get_price_unsafe(id:FixedBytes<32>)->Result{ + return Self::_get_price_unsafe(id) + } + + fn get_price_no_older_than(id:FixedBytes<32>, age:U8) -> Result { + + } + fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result { + + } + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { + + } + fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error> { + + } + fn get_update_fee(&self, update_data: Vec) -> Result { + + } + fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>{ + + } + + fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result { + + } +} + +impl AbstractPyth { + fn _get_price_unsafe(id:FixedBytes<32>)->Result { + //self._ipyth + //call_helper::(&self._ipyth, id, ()).map_err(|e| e.into()) + + } + fn _get_price_no_older_than(id:Vec>, age:U8) -> Result{ + + } + // fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; + // fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> ; + // fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; + // fn get_update_fee(&self, update_data: Vec) -> Result ; + // fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; + // fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs new file mode 100644 index 0000000000..8f4a813da9 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -0,0 +1,117 @@ +use alloy_sol_types::sol; +use stylus_proc::SolidityError; +use stylus_sdk::call::MethodError; + +sol! { + // Function arguments are invalid (e.g., the arguments lengths mismatch) + // Signature: 0xa9cb9e0d + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidArgument(); + + // Update data is coming from an invalid data source. + // Signature: 0xe60dce71 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidUpdateDataSource(); + + // Update data is invalid (e.g., deserialization error) + // Signature: 0xe69ffece + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidUpdateData(); + + // Insufficient fee is paid to the method. + // Signature: 0x025dbdd4 + #[derive(Debug)] + #[allow(missing_docs)] + error InsufficientFee(); + + // There is no fresh update, whereas expected fresh updates. + // Signature: 0xde2c57fa + #[derive(Debug)] + #[allow(missing_docs)] + error NoFreshUpdate(); + + // There is no price feed found within the given range or it does not exists. + // Signature: 0x45805f5d + #[derive(Debug)] + #[allow(missing_docs)] + error PriceFeedNotFoundWithinRange(); + + // Price feed not found or it is not pushed on-chain yet. + // Signature: 0x14aebe68 + #[derive(Debug)] + #[allow(missing_docs)] + error PriceFeedNotFound(); + + // Requested price is stale. + // Signature: 0x19abf40e + #[derive(Debug)] + #[allow(missing_docs)] + error StalePrice(); + + // Given message is not a valid Wormhole VAA. + // Signature: 0x2acbe915 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidWormholeVaa(); + + // Governance message is invalid (e.g., deserialization error). + // Signature: 0x97363b35 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceMessage(); + + // Governance message is not for this contract. + // Signature: 0x63daeb77 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceTarget(); + + // Governance message is coming from an invalid data source. + // Signature: 0x360f2d87 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidGovernanceDataSource(); + + // Governance message is old. + // Signature: 0x88d1b847 + #[derive(Debug)] + #[allow(missing_docs)] + error OldGovernanceMessage(); + + // The wormhole address to set in SetWormholeAddress governance is invalid. + // Signature: 0x13d3ed82 + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidWormholeAddressToSet(); +} + + +/// A Pausable error. +#[derive(SolidityError, Debug)] +pub enum IPythError { + InvalidArgument(InvalidArgument), + InvalidUpdateDataSource(InvalidUpdateDataSource), + InvalidUpdateData(InvalidUpdateData), + InsufficientFee(InsufficientFee), + NoFreshUpdate(NoFreshUpdate), + PriceFeedNotFoundWithinRange(PriceFeedNotFoundWithinRange), + PriceFeedNotFound(PriceFeedNotFound), + StalePrice(StalePrice), + InvalidWormholeVaa(InvalidWormholeVaa), + InvalidGovernanceMessage(InvalidGovernanceMessage), + InvalidGovernanceTarget(InvalidGovernanceTarget), + InvalidGovernanceDataSource(InvalidGovernanceDataSource), + OldGovernanceMessage(OldGovernanceMessage), + InvalidWormholeAddressToSet(InvalidWormholeAddressToSet) + +} + +impl MethodError for IPythError { + fn encode(self) -> alloc::vec::Vec { + self.into() + } +} + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs new file mode 100644 index 0000000000..e7500af097 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs @@ -0,0 +1,18 @@ +use alloc::vec::Vec; +use alloy_primitives::{Bytes, FixedBytes,U64, U8}; +use crate::pyth::structs::{Price, PriceFeed}; + +pub trait IPyth { + type Error: Into>; + fn get_price_unsafe(id:FixedBytes<32>)->Result; + fn get_price_no_older_than(id:FixedBytes<32>, age:U8) -> Result; + fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> ; + fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; + fn get_update_fee(&self, update_data: Vec) -> Result ; + fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; + fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; +} + + + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs new file mode 100644 index 0000000000..30ac640d74 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -0,0 +1,7 @@ + + +pub mod errors; +pub mod structs; +pub mod ipyth; +pub mod abstract_pyth; +pub mod solidity; \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs new file mode 100644 index 0000000000..d8cbd96b2a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs @@ -0,0 +1,142 @@ +//! Various Solidity definitions, including ABI-compatible interfaces, events, functions, etc. + +use alloy_sol_types::sol; +use stylus_proc::sol_storage; +use crate::pyth::structs::{Price, PriceFeed}; + + +sol! { + + /// @notice Returns the price of a price feed without any sanity checks. + /// @dev This function returns the most recent price update in this contract without any recency checks. + /// This function is unsafe as the returned price update may be arbitrarily far in the past. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use `getPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + + function getPriceUnsafe( + bytes32 id + ) external ; + + /// @notice Returns the price that is no older than `age` seconds of the current time. + /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPriceNoOlderThan( + bytes32 id, + uint age + ) external ; + + /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. + /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. + /// However, if the price is not recent this function returns the latest available price. + /// + /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that + /// the returned price is recent or useful for any particular application. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceUnsafe( + bytes32 id + ) external ; + + /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds + /// of the current time. + /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) external ; + + /// @notice Update price feeds with given update messages. + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// Prices will be updated if they are more recent than the current stored prices. + /// The call will succeed even if the update is not the most recent. + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + function updatePriceFeeds(bytes[] calldata updateData) external payable; + + /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is + /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the + /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime + /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have + /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. + /// Otherwise, it calls updatePriceFeeds method to update the prices. + /// + /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + /// @notice Returns the required fee to update an array of price updates. + /// @param updateData Array of price update data. + /// @return feeAmount The required fee in Wei. + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + + /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published + /// within `minPublishTime` and `maxPublishTime`. + /// + /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; + /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they + /// are more recent than the current stored prices. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external ; + + /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are + /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, + /// this method will return the first update. This method may store the price updates on-chain, if they + /// are more recent than the current stored prices. + /// + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range and uniqueness condition. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable; + +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs new file mode 100644 index 0000000000..212326324f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs @@ -0,0 +1,44 @@ +use alloc::vec::Vec; +use alloy_primitives::{ I32, I64, U256}; +use stylus_proc::sol_storage; +use stylus_sdk::call::Error; + + +sol_storage! { + pub struct Price { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publish_time; + } + + pub struct PriceFeed { + // The price ID. + bytes32 id; + // Latest available price + Price price; + // Latest available exponentially-weighted moving average price + Price ema_price; + } +} + +impl Price { + // pub fn new(&self, price:I64, conf:U64, expp :I32, publish_time:U256) -> Price { + // self.conf.set(conf); + // self.publish_time.set(publish_time); + // return self.clone(); + // } + + + pub fn convert_to_uint(&self) -> Result { + if self.price.gt(&I64::ZERO) || self.expo.gt(&I32::ZERO) || self.expo.lt(&I32::MIN) { + return Err(Error::Revert(Vec::new())); + } + //let price_decimal = self.expo.as_u32(); + return Ok(U256::from(10000000000000000000u128 * self.price.as_u64() as u128)) + } +} \ No newline at end of file From e94d4c5cedd05c8f01fcb473aed9e5a7b2c265ba Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 24 Sep 2024 22:56:40 +0000 Subject: [PATCH 013/160] chore : helpers functions --- .../sdk/stylus/contracts/src/utils/helpers.rs | 63 +++++++++++++++++++ .../sdk/stylus/contracts/src/utils/mod.rs | 1 + 2 files changed, 64 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs new file mode 100644 index 0000000000..599bc375de --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs @@ -0,0 +1,63 @@ +use stylus_sdk::{ + alloy_primitives::Address, + call::{call, delegate_call, static_call}, + storage::TopLevelStorage, +}; +use alloy_sol_types::{SolCall, SolType}; +use alloc::vec::Vec; + + +/// The revert message when failing to decode the data +/// returned by an external contract call +pub const CALL_RETDATA_DECODING_ERROR_MESSAGE: &[u8] = b"error decoding retdata"; + +/// Maps an error returned from an external contract call to a `Vec`, +/// which is the expected return type of external contract methods. +pub fn map_call_error(e: stylus_sdk::call::Error) -> Vec { + match e { + stylus_sdk::call::Error::Revert(msg) => msg, + stylus_sdk::call::Error::AbiDecodingFailed(_) => { + CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec() + } + } +} + +/// Performs a `delegate call` to the given address, calling the function +/// defined as a `SolCall` with the given arguments. + +pub fn delegate_call_helper( + storage: &mut impl TopLevelStorage, + address: Address, + args: as SolType>::RustType, +) -> Result> { + let calldata = C::new(args).abi_encode(); + let res = unsafe { delegate_call(storage, address, &calldata).map_err(map_call_error)? }; + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} + +/// Performs a `staticcall` to the given address, calling the function defined as a `SolCall` with the given arguments + +pub fn static_call_helper( + storage: &impl TopLevelStorage, + address: Address, + args: as SolType>::RustType, +) -> Result> { + let calldata = C::new(args).abi_encode(); + let res = static_call(storage, address, &calldata).map_err(map_call_error)?; + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} + +/// Performs a `call` to the given address, calling the function +/// defined as a `SolCall` with the given arguments. +pub fn call_helper( + storage: &mut impl TopLevelStorage, + address: Address, + args: as SolType>::RustType, +) -> Result> { + let calldata = C::new(args).abi_encode(); + let res = call(storage, address, &calldata).map_err(map_call_error)?; + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs new file mode 100644 index 0000000000..eadef2b964 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod helpers; \ No newline at end of file From c891962e35e97d26279ccb16c0c5fab113ecf5c9 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 24 Sep 2024 22:56:57 +0000 Subject: [PATCH 014/160] refacor : refactord code --- .../sdk/stylus/contracts/src/abstract_pyth.rs | 5 - .../sdk/stylus/contracts/src/errors.rs | 116 ------------------ .../sdk/stylus/contracts/src/ipyth.rs | 36 ------ .../ethereum/sdk/stylus/contracts/src/lib.rs | 12 +- .../sdk/stylus/contracts/src/structs.rs | 38 ------ 5 files changed, 5 insertions(+), 202 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/errors.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/structs.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs deleted file mode 100644 index f8253d2b24..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/abstract_pyth.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::ipyth::IPyth; - -impl AbstractPyth for IPyth { - -} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs deleted file mode 100644 index b2f38e4a33..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/errors.rs +++ /dev/null @@ -1,116 +0,0 @@ -use alloy_sol_types::sol; -use stylus_proc::SolidityError; -use stylus_sdk::call::MethodError; - -sol! { - // Function arguments are invalid (e.g., the arguments lengths mismatch) - // Signature: 0xa9cb9e0d - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidArgument(); - - // Update data is coming from an invalid data source. - // Signature: 0xe60dce71 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidUpdateDataSource(); - - // Update data is invalid (e.g., deserialization error) - // Signature: 0xe69ffece - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidUpdateData(); - - // Insufficient fee is paid to the method. - // Signature: 0x025dbdd4 - #[derive(Debug)] - #[allow(missing_docs)] - error InsufficientFee(); - - // There is no fresh update, whereas expected fresh updates. - // Signature: 0xde2c57fa - #[derive(Debug)] - #[allow(missing_docs)] - error NoFreshUpdate(); - - // There is no price feed found within the given range or it does not exists. - // Signature: 0x45805f5d - #[derive(Debug)] - #[allow(missing_docs)] - error PriceFeedNotFoundWithinRange(); - - // Price feed not found or it is not pushed on-chain yet. - // Signature: 0x14aebe68 - #[derive(Debug)] - #[allow(missing_docs)] - error PriceFeedNotFound(); - - // Requested price is stale. - // Signature: 0x19abf40e - #[derive(Debug)] - #[allow(missing_docs)] - error StalePrice(); - - // Given message is not a valid Wormhole VAA. - // Signature: 0x2acbe915 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidWormholeVaa(); - - // Governance message is invalid (e.g., deserialization error). - // Signature: 0x97363b35 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidGovernanceMessage(); - - // Governance message is not for this contract. - // Signature: 0x63daeb77 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidGovernanceTarget(); - - // Governance message is coming from an invalid data source. - // Signature: 0x360f2d87 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidGovernanceDataSource(); - - // Governance message is old. - // Signature: 0x88d1b847 - #[derive(Debug)] - #[allow(missing_docs)] - error OldGovernanceMessage(); - - // The wormhole address to set in SetWormholeAddress governance is invalid. - // Signature: 0x13d3ed82 - #[derive(Debug)] - #[allow(missing_docs)] - error InvalidWormholeAddressToSet(); -} - - -/// A Pausable error. -#[derive(SolidityError, Debug)] -pub enum IPythError { - InvalidArgument(InvalidArgument), - InvalidUpdateDataSource(InvalidUpdateDataSource), - InvalidUpdateData(InvalidUpdateData), - InsufficientFee(InsufficientFee), - NoFreshUpdate(NoFreshUpdate), - PriceFeedNotFoundWithinRange(PriceFeedNotFoundWithinRange), - PriceFeedNotFound(PriceFeedNotFound), - StalePrice(StalePrice), - InvalidWormholeVaa(InvalidWormholeVaa), - InvalidGovernanceMessage(InvalidGovernanceMessage), - InvalidGovernanceTarget(InvalidGovernanceTarget), - InvalidGovernanceDataSource(InvalidGovernanceDataSource), - OldGovernanceMessage(OldGovernanceMessage), - InvalidWormholeAddressToSet(InvalidWormholeAddressToSet) - -} - -impl MethodError for IPythError { - fn encode(self) -> alloc::vec::Vec { - self.into() - } -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs deleted file mode 100644 index 636bc6d52d..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/ipyth.rs +++ /dev/null @@ -1,36 +0,0 @@ -use alloc::vec::Vec; -use alloy_primitives::{U64, U8}; -use alloy_primitives::{Bytes, FixedBytes}; -use stylus_proc::{public,external, sol_interface}; -use crate::structs::{Price, PriceFeed}; - - -sol_interface! { - interface IPythMethod { - function updatePriceFeeds(bytes[] calldata updateData) external payable; - function updatePriceFeedsIfNecessary( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64[] calldata publishTimes - ) external payable; - - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount); - } -} - -pub trait IPyth { - type Error: Into>; - fn get_price_unsafe()->Result; - fn get_price_no_older_than(id:Vec>, age:U8) -> Result; - fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; - fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; - fn get_update_fee(&self, update_data: Vec) -> Result; - fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; - fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; -} - - - diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 1e627d7d71..f7cd26b4be 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -1,6 +1,4 @@ - - #![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] @@ -9,8 +7,8 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; -pub mod structs; -pub mod errors; -pub mod ipyth; -pub mod abstract_pyth; -pub mod mock_pyth; +pub mod utils; +//pub mod structs; +pub mod pyth; + +// pub mod mock; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs b/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs deleted file mode 100644 index a6743ef166..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/structs.rs +++ /dev/null @@ -1,38 +0,0 @@ -use alloc::vec::Vec; -use alloy_primitives::{ I32, I64, U256}; -use stylus_proc::{sol_storage}; -use stylus_sdk::call::Error; - - -sol_storage! { - pub struct Price { - // Price - int64 price; - // Confidence interval around the price - uint64 conf; - // Price exponent - int32 expo; - // Unix timestamp describing when the price was published - uint publish_time; - } - - pub struct PriceFeed { - // The price ID. - bytes32 id; - // Latest available price - Price price; - // Latest available exponentially-weighted moving average price - Price ema_price; - } -} - -impl Price { - - pub fn convert_to_uint(&self) -> Result { - if self.price.gt(&I64::ZERO) || self.expo.gt(&I32::ZERO) || self.expo.lt(&I32::MIN) { - return Err(Error::Revert(Vec::new())); - } - //let price_decimal = self.expo.as_u32(); - return Ok(U256::from(10000000000000000000u128 * self.price.as_u64() as u128)) - } -} \ No newline at end of file From 93a72913a172d8d0282e58287ffbc1e58718bf52 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 27 Sep 2024 04:13:15 +0000 Subject: [PATCH 015/160] chore: removed struct type , implemented abstract_pyth --- .../contracts/src/pyth/abstract_pyth.rs | 129 +++++++++++++----- .../sdk/stylus/contracts/src/pyth/errors.rs | 6 +- .../sdk/stylus/contracts/src/pyth/events.rs | 18 +++ .../sdk/stylus/contracts/src/pyth/structs.rs | 44 ------ 4 files changed, 113 insertions(+), 84 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs index 0cb120123c..44cc28e314 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs @@ -1,66 +1,123 @@ -use stylus_proc::{public, sol_storage}; +use stylus_sdk::{block, prelude::*, storage::TopLevelStorage}; +use crate::{pyth::solidity::{ + getEmaPriceUnsafeCall, getPriceUnsafeCall, Price,PriceFeed, updatePriceFeedsCall, + StoragePriceFeed +}, utils::helpers::delegate_call_helper}; -use crate::pyth::ipyth::IPyth; -use crate::pyth::errors::IPythError; -use crate::pyth::structs::{Price, PriceFeed}; use alloc::vec::Vec; -use alloy_primitives::{Bytes, FixedBytes,U64, U8}; - +use alloy_primitives::{Bytes, FixedBytes, U256, U64}; use crate::utils::helpers::call_helper; +use core::borrow::BorrowMut; +use super::errors::{IPythError, InvalidArgument, NoFreshUpdate, PriceFeedNotFound, StalePrice}; + -use super::solidity::{getPriceUnsafeCall}; sol_storage! { pub struct AbstractPyth { address _ipyth; + mapping(bytes32 => StoragePriceFeed) price_feeds; } } -#[public] -impl IPyth for AbstractPyth { - type Error = IPythError; - fn get_price_unsafe(id:FixedBytes<32>)->Result{ - return Self::_get_price_unsafe(id) +impl AbstractPyth { + fn _query_price_feed( + self, + id:FixedBytes<32> + )->Result>{ + let price_found = self.price_feeds.getter(id).to_price_feed(); + if price_found.id.is_empty() { + return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound{}).into()); + } + else { + return Ok(price_found); + } } - fn get_price_no_older_than(id:FixedBytes<32>, age:U8) -> Result { - + fn _get_price_unsafe>( + self, + storage: &mut S, + id:FixedBytes<32> + )->Result> { + let (price,) = call_helper::(storage, self._ipyth.get(), (id,))?.into(); + Ok(price) } - fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result { + fn _get_price_no_older_than>( + self, + storage: &mut S, + id:FixedBytes<32>, + age:U256 + ) -> Result>{ + let price = self._get_ema_price_unsafe(storage, id)?; + if Self::_diff(U256::from(block::timestamp()), price.publishTime) > age { + return Err(IPythError::StalePrice( StalePrice{}).into()); + } + Ok(price) } - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { + fn _get_ema_price_unsafe>( + self, + storage: &mut S, + id:FixedBytes<32>, + ) -> Result>{ + let (price,) = call_helper::(storage, self._ipyth.get(),(id,))?.into(); + Ok(price) } - fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error> { + fn _get_ema_price_no_older_than>( + self, + storage: &mut S, + id:FixedBytes<32>, + age:U256 + ) -> Result>{ + let (price,) = call_helper::(storage, self._ipyth.get(),(id,))?.into(); + if Self::_diff(U256::from(block::timestamp()), price.publishTime) > age { + return Err(IPythError::StalePrice(StalePrice{}).into()); + } + Ok(price) } - fn get_update_fee(&self, update_data: Vec) -> Result { - } - fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>{ + fn _update_price_feeds_if_necessary>( + self, + storage: &mut S, + update_data:Vec, + price_ids: Vec>, + publish_times: Vec + ) -> Result<(), Vec> { + if update_data.len() != publish_times.len() { + return Err(IPythError::InvalidArgument(InvalidArgument{}).into()); + } + + // for i in 0..price_ids.len() { + // // if + // // !priceFeedExists(priceIds[i]) || + // // queryPriceFeed(priceIds[i]).price.publishTime < publishTimes[i] + // // { + // // updatePriceFeeds(updateData); + // // return; + // } + + // } + return Err(IPythError::NoFreshUpdate(NoFreshUpdate{}).into()); } - fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result { - + fn _update_price_feeds>( + self, + storage: &mut S, + update_data:Vec, + + )-> Result<(), Vec>{ + delegate_call_helper::(storage, self._ipyth.get(), (update_data,))?; + Ok(()) } -} -impl AbstractPyth { - fn _get_price_unsafe(id:FixedBytes<32>)->Result { - //self._ipyth - //call_helper::(&self._ipyth, id, ()).map_err(|e| e.into()) - + fn _diff(x:U256, y:U256) -> U256 { + if x > y { + return x -y ; + } + return y - x ; } - fn _get_price_no_older_than(id:Vec>, age:U8) -> Result{ - } - // fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; - // fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> ; - // fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; - // fn get_update_fee(&self, update_data: Vec) -> Result ; - // fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; - // fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs index 8f4a813da9..9dd892772c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -1,6 +1,5 @@ use alloy_sol_types::sol; -use stylus_proc::SolidityError; -use stylus_sdk::call::MethodError; +use stylus_sdk::{call::MethodError, prelude::*}; sol! { // Function arguments are invalid (e.g., the arguments lengths mismatch) @@ -88,9 +87,8 @@ sol! { error InvalidWormholeAddressToSet(); } - /// A Pausable error. -#[derive(SolidityError, Debug)] +#[derive(SolidityError ,Debug)] pub enum IPythError { InvalidArgument(InvalidArgument), InvalidUpdateDataSource(InvalidUpdateDataSource), diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs new file mode 100644 index 0000000000..f44225f33a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs @@ -0,0 +1,18 @@ +//! Various Solidity definitions, including ABI-compatible interfaces, events, functions, etc. + +use alloy_sol_types::sol; + + +sol! { + /// @dev Emitted when the price feed with `id` has received a fresh update. + /// @param id The Pyth Price Feed ID. + /// @param publishTime Publish time of the given price update. + /// @param price Price of the given price update. + /// @param conf Confidence interval of the given price update. + event PriceFeedUpdate( + bytes32 indexed id, + uint64 publishTime, + int64 price, + uint64 conf + ); +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs deleted file mode 100644 index 212326324f..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/structs.rs +++ /dev/null @@ -1,44 +0,0 @@ -use alloc::vec::Vec; -use alloy_primitives::{ I32, I64, U256}; -use stylus_proc::sol_storage; -use stylus_sdk::call::Error; - - -sol_storage! { - pub struct Price { - // Price - int64 price; - // Confidence interval around the price - uint64 conf; - // Price exponent - int32 expo; - // Unix timestamp describing when the price was published - uint publish_time; - } - - pub struct PriceFeed { - // The price ID. - bytes32 id; - // Latest available price - Price price; - // Latest available exponentially-weighted moving average price - Price ema_price; - } -} - -impl Price { - // pub fn new(&self, price:I64, conf:U64, expp :I32, publish_time:U256) -> Price { - // self.conf.set(conf); - // self.publish_time.set(publish_time); - // return self.clone(); - // } - - - pub fn convert_to_uint(&self) -> Result { - if self.price.gt(&I64::ZERO) || self.expo.gt(&I32::ZERO) || self.expo.lt(&I32::MIN) { - return Err(Error::Revert(Vec::new())); - } - //let price_decimal = self.expo.as_u32(); - return Ok(U256::from(10000000000000000000u128 * self.price.as_u64() as u128)) - } -} \ No newline at end of file From 65ea9a5fa3ed873228e4d9ca034d989402ad91b5 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 27 Sep 2024 04:13:37 +0000 Subject: [PATCH 016/160] chore: created storage price and storage price feed --- .../sdk/stylus/contracts/src/pyth/solidity.rs | 114 +++++++++++++++--- 1 file changed, 97 insertions(+), 17 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs index d8cbd96b2a..ac984254fc 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs @@ -1,12 +1,53 @@ -//! Various Solidity definitions, including ABI-compatible interfaces, events, functions, etc. +use core::fmt::Binary; +use alloy_primitives::I64; use alloy_sol_types::sol; -use stylus_proc::sol_storage; -use crate::pyth::structs::{Price, PriceFeed}; +use stylus_sdk::prelude::sol_storage; + +sol_storage! { + pub struct StoragePrice { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publish_time; + } + + pub struct StoragePriceFeed { + bytes32 id; + StoragePrice price; + StoragePrice ema_price; + } +} sol! { - + + struct Price { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publishTime; + } + + // PriceFeed represents a current aggregate price from pyth publisher feeds. + + struct PriceFeed { + // The price ID. + bytes32 id; + // Latest available price + Price price; + // Latest available exponentially-weighted moving average price + Price emaPrice; + } + /// @notice Returns the price of a price feed without any sanity checks. /// @dev This function returns the most recent price update in this contract without any recency checks. /// This function is unsafe as the returned price update may be arbitrarily far in the past. @@ -14,21 +55,20 @@ sol! { /// Users of this function should check the `publishTime` in the price to ensure that the returned price is /// sufficiently recent for their application. If you are considering using this function, it may be /// safer / easier to use `getPriceNoOlderThan`. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. - + /// @return price - please read the documentation of Price to understand how to use this safely. function getPriceUnsafe( bytes32 id - ) external ; + ) external view returns (Price memory price); /// @notice Returns the price that is no older than `age` seconds of the current time. /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently /// recently. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + /// @return price - please read the documentation of Price to understand how to use this safely. function getPriceNoOlderThan( bytes32 id, uint age - ) external ; + ) external view returns (Price memory price); /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. @@ -40,21 +80,21 @@ sol! { /// Users of this function should check the `publishTime` in the price to ensure that the returned price is /// sufficiently recent for their application. If you are considering using this function, it may be /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + /// @return price - please read the documentation of Price to understand how to use this safely. function getEmaPriceUnsafe( bytes32 id - ) external ; + ) external view returns (Price memory price); /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds /// of the current time. /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently /// recently. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + /// @return price - please read the documentation of Price to understand how to use this safely. function getEmaPriceNoOlderThan( bytes32 id, uint age - ) external ; + ) external view returns (Price memory price); /// @notice Update price feeds with given update messages. /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling @@ -117,7 +157,7 @@ sol! { bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external ; + ) external payable returns (PriceFeed[] memory priceFeeds); /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, @@ -137,6 +177,46 @@ sol! { bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external payable; - -} \ No newline at end of file + ) external payable returns (PriceFeed[] memory priceFeeds); + + + function queryPriceFeed( + bytes32 id + ) public view virtual returns (PriceFeed memory priceFeed); + + function priceFeedExists( + bytes32 id + ) public view virtual returns (bool exists); + + + /// @notice This function is deprecated and is only kept for backward compatibility. + function getValidTimePeriod() + public + view + virtual + returns (uint validTimePeriod); + + } + +impl StoragePrice { + pub fn to_price(&self) ->Price { + Price { + price:self.price.get().as_i64(), + conf: self.conf.get().to(), + expo: self.expo.get().as_i32(), + publishTime: self.publish_time.get() + } + } +} + +impl StoragePriceFeed { + pub fn to_price_feed(&self)-> PriceFeed { + PriceFeed { + id: self.id.get(), + price: self.price.to_price(), + emaPrice: self.ema_price.to_price() + } + } +} + + \ No newline at end of file From 28019ed962b4393e64b0c870535bef57d59e8dd0 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 27 Sep 2024 04:13:57 +0000 Subject: [PATCH 017/160] chore: integrating storage price and storage price feed --- .../ethereum/sdk/stylus/contracts/Cargo.toml | 1 - .../ethereum/sdk/stylus/contracts/src/mock.rs | 33 ------------------- .../sdk/stylus/contracts/src/mock_pyth.rs | 0 .../sdk/stylus/contracts/src/pyth/ipyth.rs | 4 +-- .../sdk/stylus/contracts/src/pyth/mod.rs | 6 ++-- 5 files changed, 5 insertions(+), 39 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/mock.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index 0623bdf231..b2f1cf3290 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -12,7 +12,6 @@ alloy-primitives.workspace = true alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true -stylus-proc.workspace = true [features] diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs deleted file mode 100644 index c4844de889..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/mock.rs +++ /dev/null @@ -1,33 +0,0 @@ -use stylus_sdk::{alloy_primitives::{U256, FixedBytes}, prelude::*}; -use crate::{ - errors::IPythError, - ipyth::{AbstractPyth, IPyth}, -}; -use stylus_proc::storage; -use stylus_sdk::{call::Error, storage::{StorageMap,StorageB32, StorageU256, StorageKey}}; - - - -sol_storage! { - #[entrypoint] - pub struct MockPyth { - mapping(bytes32 => PriceFeed) price_feeds; - uint single_update_fee_in_wei; - uint valid_time_period; - #[borrow] - AbstractPyth abstract_pyth; - } -} - -#[public] -#[inherit(AbstractPyth)] -impl MockPyth { - pub fn initialize(&mut self, valid_time_period:U256, single_update_fee_in_wei:U256) { - self.valid_time_period.set(valid_time_period); - self.single_update_fee_in_wei.set(single_update_fee_in_wei); - } - - // pub fn get_price_feed(&self, id:FixedBytes<32>) -> Result { - // return self::AbstractPyth.query_price_feed(id) - // } -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/mock_pyth.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs index e7500af097..0af222ea5e 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs @@ -1,10 +1,10 @@ use alloc::vec::Vec; use alloy_primitives::{Bytes, FixedBytes,U64, U8}; -use crate::pyth::structs::{Price, PriceFeed}; +use crate::pyth::solidity::{Price, PriceFeed}; pub trait IPyth { type Error: Into>; - fn get_price_unsafe(id:FixedBytes<32>)->Result; + fn get_price_unsafe(self, id:FixedBytes<32>)->Result; fn get_price_no_older_than(id:FixedBytes<32>, age:U8) -> Result; fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> ; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 30ac640d74..ffc3a51d84 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,7 +1,7 @@ pub mod errors; -pub mod structs; -pub mod ipyth; pub mod abstract_pyth; -pub mod solidity; \ No newline at end of file +pub mod solidity; +pub mod events; +pub mod ipyth; \ No newline at end of file From 81f45ae731c0ebe8bfc6f38f19f65400dd844f1d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 3 Oct 2024 06:16:13 +0000 Subject: [PATCH 018/160] chore: fixed error created by storage type --- target_chains/ethereum/sdk/stylus/Cargo.lock | 1057 ++++++++++++++++- target_chains/ethereum/sdk/stylus/Cargo.toml | 44 +- .../ethereum/sdk/stylus/contracts/Cargo.toml | 4 +- .../ethereum/sdk/stylus/contracts/src/lib.rs | 10 +- .../contracts/src/pyth/abstract_pyth.rs | 123 -- .../sdk/stylus/contracts/src/pyth/errors.rs | 11 +- .../sdk/stylus/contracts/src/pyth/events.rs | 18 +- .../sdk/stylus/contracts/src/pyth/ipyth.rs | 40 +- .../sdk/stylus/contracts/src/pyth/mock.rs | 204 ++++ .../sdk/stylus/contracts/src/pyth/mod.rs | 177 ++- .../contracts/src/pyth/pyth_aggregator_V3.rs | 0 .../sdk/stylus/contracts/src/pyth/solidity.rs | 186 +-- .../sdk/stylus/contracts/src/utils/helpers.rs | 46 +- .../sdk/stylus/contracts/src/utils/mod.rs | 2 +- .../ethereum/sdk/stylus/mock/Cargo.toml | 12 +- .../sdk/stylus/mock/example/pythtest.rs | 78 ++ .../ethereum/sdk/stylus/mock/privateKey.txt | 0 .../ethereum/sdk/stylus/mock/src/lib.rs | 44 + .../ethereum/sdk/stylus/mock/src/main.rs | 7 +- .../ethereum/sdk/stylus/netlify.toml | 4 + .../ethereum/sdk/stylus/rustfmt.toml | 8 + 21 files changed, 1653 insertions(+), 422 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs create mode 100644 target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs create mode 100644 target_chains/ethereum/sdk/stylus/mock/privateKey.txt create mode 100644 target_chains/ethereum/sdk/stylus/netlify.toml create mode 100644 target_chains/ethereum/sdk/stylus/rustfmt.toml diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index bb66fe04c4..bcf75a6677 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -17,16 +17,32 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" dependencies = [ + "alloy-rlp", "bytes", "cfg-if 1.0.0", "const-hex", "derive_more", "hex-literal", "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", "ruint", + "serde", "tiny-keccak", ] +[[package]] +name = "alloy-rlp" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "alloy-sol-macro" version = "0.7.7" @@ -38,7 +54,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -54,7 +70,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "syn-solidity", "tiny-keccak", ] @@ -70,7 +86,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "syn-solidity", ] @@ -83,13 +99,182 @@ dependencies = [ "alloy-primitives", "alloy-sol-macro", "const-hex", + "serde", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" @@ -97,6 +282,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -106,6 +303,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.5.0" @@ -118,6 +321,15 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cc" +version = "1.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -132,9 +344,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-hex" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -144,15 +356,10 @@ dependencies = [ ] [[package]] -name = "contracts" -version = "0.0.1" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "mini-alloc", - "stylus-proc", - "stylus-sdk", -] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -184,6 +391,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[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.6" @@ -194,6 +413,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -214,8 +443,17 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.77", + "rustc_version 0.4.1", + "syn 2.0.79", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", ] [[package]] @@ -225,7 +463,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -234,16 +474,116 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[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", + "spki", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[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", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "example" version = "0.0.1" +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -252,13 +592,36 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[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.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -278,22 +641,73 @@ 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 = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + [[package]] name = "keccak" version = "0.1.5" @@ -303,6 +717,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-const" version = "0.2.0" @@ -317,9 +741,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -327,6 +751,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "memchr" version = "2.7.4" @@ -334,36 +764,138 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "memory_units" -version = "0.4.0" +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "mini-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14eacc4bfcf10da9b6d5185ef51b2dc75434afac34b4d8aabe40f6b141ee071c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mock" +version = "0.0.1" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "mini-alloc 0.4.2", + "pyth-stylus", + "stylus-proc", + "stylus-sdk", +] + +[[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", +] + +[[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", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "mini-alloc" -version = "0.4.2" +name = "pest" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ - "cfg-if 1.0.0", - "wee_alloc", + "memchr", + "thiserror", + "ucd-trie", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "autocfg", - "libm", + "der", + "spki", ] [[package]] -name = "paste" -version = "1.0.15" +name = "portable-atomic" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" @@ -374,6 +906,26 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -413,14 +965,36 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ + "bit-set", + "bit-vec", "bitflags", + "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", "unarray", ] +[[package]] +name = "pyth-stylus" +version = "0.0.1" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "mini-alloc 0.4.2", + "stylus-sdk", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.37" @@ -430,12 +1004,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -454,6 +1036,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rand_xorshift" @@ -466,9 +1051,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -478,9 +1063,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -489,9 +1074,29 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] [[package]] name = "ruint" @@ -499,8 +1104,18 @@ version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", "proptest", "rand", + "rlp", "ruint-macro", "serde", "valuable", @@ -513,13 +1128,76 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.23", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", ] [[package]] @@ -528,6 +1206,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.210" @@ -545,7 +1232,18 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", ] [[package]] @@ -554,10 +1252,52 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if 1.0.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[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 = "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 = "stylus-proc" version = "0.6.0" @@ -590,9 +1330,17 @@ dependencies = [ "hex", "keccak-const", "lazy_static", + "mini-alloc 0.6.0", + "regex", "stylus-proc", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -606,9 +1354,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -624,13 +1372,52 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] name = "tests" version = "0.0.1" +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -640,12 +1427,47 @@ dependencies = [ "crunchy", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -676,6 +1498,21 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wee_alloc" version = "0.4.5" @@ -710,6 +1547,106 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[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 = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -728,7 +1665,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -736,3 +1673,17 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index b6b506dea1..eda7726318 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "contracts", "example", "tests", + "contracts", "example", "tests","mock" ] default-members = [ "contracts", @@ -29,42 +29,16 @@ pedantic = "warn" all = "warn" [workspace.dependencies] -# stylus-related -stylus-sdk = { version = "=0.6.0", default-features = false } -stylus-proc = { version = "=0.6.0", default-features = false } +alloy-primitives = "=0.7.6" +alloy-sol-types = "=0.7.6" mini-alloc = "0.4.2" +stylus-sdk = "0.6.0" +stylus-proc = "0.6.0" +hex = "0.4.3" +dotenv = "0.15.0" -alloy = { version = "0.1.4", features = [ - "contract", - "network", - "providers", - "provider-http", - "rpc-client", - "rpc-types-eth", - "signer-local", - "getrandom", -] } -# Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need -# to keep both versions for compatibility with the Stylus SDK. Once they start -# using `alloy` we can remove these. -alloy-primitives = { version = "0.7.6", default-features = false } -alloy-sol-types = { version = "0.7.6", default-features = false } - -const-hex = { version = "1.11.1", default-features = false } -eyre = "0.6.8" -keccak-const = "0.2.0" -koba = "0.2.0" -once_cell = "1.19.0" -rand = "0.8.5" -regex = "1.10.4" -tiny-keccak = { version = "2.0.2", features = ["keccak"] } -tokio = { version = "1.12.0", features = ["full"] } - -# procedural macros -syn = { version = "2.0.58", features = ["full"] } -proc-macro2 = "1.0.79" -quote = "1.0.35" - +# members +pyth-stylus = { path = "contracts" } [profile.release] codegen-units = 1 diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index b2f1cf3290..c8bafc6c4a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pyth-sdk" +name = "pyth-stylus" authors.workspace = true license.workspace = true keywords.workspace = true @@ -25,3 +25,5 @@ crate-type = ["lib", "cdylib"] [lints] workspace = true + + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index f7cd26b4be..2db5c5f00c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -1,14 +1,14 @@ - #![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] + extern crate alloc; -#[global_allocator] -static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +// #[global_allocator] +// static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; -pub mod utils; +pub mod utils; //pub mod structs; -pub mod pyth; +pub mod pyth; // pub mod mock; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs deleted file mode 100644 index 44cc28e314..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/abstract_pyth.rs +++ /dev/null @@ -1,123 +0,0 @@ -use stylus_sdk::{block, prelude::*, storage::TopLevelStorage}; -use crate::{pyth::solidity::{ - getEmaPriceUnsafeCall, getPriceUnsafeCall, Price,PriceFeed, updatePriceFeedsCall, - StoragePriceFeed -}, utils::helpers::delegate_call_helper}; - -use alloc::vec::Vec; -use alloy_primitives::{Bytes, FixedBytes, U256, U64}; -use crate::utils::helpers::call_helper; -use core::borrow::BorrowMut; -use super::errors::{IPythError, InvalidArgument, NoFreshUpdate, PriceFeedNotFound, StalePrice}; - - - -sol_storage! { - pub struct AbstractPyth { - address _ipyth; - mapping(bytes32 => StoragePriceFeed) price_feeds; - } -} - - -impl AbstractPyth { - fn _query_price_feed( - self, - id:FixedBytes<32> - )->Result>{ - let price_found = self.price_feeds.getter(id).to_price_feed(); - if price_found.id.is_empty() { - return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound{}).into()); - } - else { - return Ok(price_found); - } - } - - fn _get_price_unsafe>( - self, - storage: &mut S, - id:FixedBytes<32> - )->Result> { - let (price,) = call_helper::(storage, self._ipyth.get(), (id,))?.into(); - Ok(price) - } - - fn _get_price_no_older_than>( - self, - storage: &mut S, - id:FixedBytes<32>, - age:U256 - ) -> Result>{ - let price = self._get_ema_price_unsafe(storage, id)?; - if Self::_diff(U256::from(block::timestamp()), price.publishTime) > age { - return Err(IPythError::StalePrice( StalePrice{}).into()); - } - Ok(price) - } - - fn _get_ema_price_unsafe>( - self, - storage: &mut S, - id:FixedBytes<32>, - ) -> Result>{ - let (price,) = call_helper::(storage, self._ipyth.get(),(id,))?.into(); - Ok(price) - } - - fn _get_ema_price_no_older_than>( - self, - storage: &mut S, - id:FixedBytes<32>, - age:U256 - ) -> Result>{ - let (price,) = call_helper::(storage, self._ipyth.get(),(id,))?.into(); - if Self::_diff(U256::from(block::timestamp()), price.publishTime) > age { - return Err(IPythError::StalePrice(StalePrice{}).into()); - } - Ok(price) - } - - - fn _update_price_feeds_if_necessary>( - self, - storage: &mut S, - update_data:Vec, - price_ids: Vec>, - publish_times: Vec - ) -> Result<(), Vec> { - if update_data.len() != publish_times.len() { - return Err(IPythError::InvalidArgument(InvalidArgument{}).into()); - } - - // for i in 0..price_ids.len() { - // // if - // // !priceFeedExists(priceIds[i]) || - // // queryPriceFeed(priceIds[i]).price.publishTime < publishTimes[i] - // // { - // // updatePriceFeeds(updateData); - // // return; - // } - - // } - return Err(IPythError::NoFreshUpdate(NoFreshUpdate{}).into()); - } - - fn _update_price_feeds>( - self, - storage: &mut S, - update_data:Vec, - - )-> Result<(), Vec>{ - delegate_call_helper::(storage, self._ipyth.get(), (update_data,))?; - Ok(()) - } - - fn _diff(x:U256, y:U256) -> U256 { - if x > y { - return x -y ; - } - return y - x ; - } - -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs index 9dd892772c..4a308f0eab 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -1,5 +1,8 @@ +use stylus_sdk::{ + call::MethodError, + prelude::*, +}; use alloy_sol_types::sol; -use stylus_sdk::{call::MethodError, prelude::*}; sol! { // Function arguments are invalid (e.g., the arguments lengths mismatch) @@ -88,7 +91,7 @@ sol! { } /// A Pausable error. -#[derive(SolidityError ,Debug)] +#[derive(SolidityError, Debug)] pub enum IPythError { InvalidArgument(InvalidArgument), InvalidUpdateDataSource(InvalidUpdateDataSource), @@ -103,8 +106,7 @@ pub enum IPythError { InvalidGovernanceTarget(InvalidGovernanceTarget), InvalidGovernanceDataSource(InvalidGovernanceDataSource), OldGovernanceMessage(OldGovernanceMessage), - InvalidWormholeAddressToSet(InvalidWormholeAddressToSet) - + InvalidWormholeAddressToSet(InvalidWormholeAddressToSet), } impl MethodError for IPythError { @@ -112,4 +114,3 @@ impl MethodError for IPythError { self.into() } } - diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs index f44225f33a..7c13ea3319 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs @@ -1,18 +1,8 @@ //! Various Solidity definitions, including ABI-compatible interfaces, events, functions, etc. -use alloy_sol_types::sol; +use stylus_sdk::alloy_sol_types::sol; +// sol! { -sol! { - /// @dev Emitted when the price feed with `id` has received a fresh update. - /// @param id The Pyth Price Feed ID. - /// @param publishTime Publish time of the given price update. - /// @param price Price of the given price update. - /// @param conf Confidence interval of the given price update. - event PriceFeedUpdate( - bytes32 indexed id, - uint64 publishTime, - int64 price, - uint64 conf - ); -} \ No newline at end of file +// event PriceFeedUpdate(); +// } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs index 0af222ea5e..ddee42fa75 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs @@ -1,18 +1,30 @@ -use alloc::vec::Vec; -use alloy_primitives::{Bytes, FixedBytes,U64, U8}; -use crate::pyth::solidity::{Price, PriceFeed}; +use crate::pyth::solidity::{Price,PriceFeed}; +use alloc::vec::Vec; +use stylus_sdk::{abi::Bytes, alloy_primitives::FixedBytes}; pub trait IPyth { type Error: Into>; - fn get_price_unsafe(self, id:FixedBytes<32>)->Result; - fn get_price_no_older_than(id:FixedBytes<32>, age:U8) -> Result; - fn get_ema_price_unsafe(id:FixedBytes<32>) -> Result; - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> ; - fn update_price_feeds_if_necessary(&mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Self::Error>; - fn get_update_fee(&self, update_data: Vec) -> Result ; - fn parse_price_feed_updates(update_data:Vec, price_ids:Vec>,min_publish_time:U64,max_publish_time:U64) -> Result, Self::Error>; - fn parse_price_feed_updates_unique(update_data:Vec,price_ids:Vec>,min_publish_time:U64,max_publish_time:U64)->Result; + fn get_price_unsafe(self, id: FixedBytes<32>) -> Result; + fn get_price_no_older_than(id: FixedBytes<32>, age: u8) -> Result; + fn get_ema_price_unsafe(id: FixedBytes<32>) -> Result; + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + fn update_price_feeds_if_necessary( + &mut self, + update_data: Vec, + price_ids: Vec>, + publish_times: Vec, + ) -> Result<(), Self::Error>; + fn get_update_fee(&self, update_data: Vec) -> Result; + fn parse_price_feed_updates( + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error>; + fn parse_price_feed_updates_unique( + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result; } - - - diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs new file mode 100644 index 0000000000..26130dc0e5 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -0,0 +1,204 @@ +use alloc::vec::Vec; +use alloy_primitives::{ Uint, U64}; +use stylus_sdk::{prelude::*,msg, abi::Bytes, alloy_primitives::{Address, FixedBytes, U256}}; +use crate::pyth::{ + errors::{IPythError, PriceFeedNotFound, InsufficientFee}, + solidity::{StoragePriceFeed}, + //events::PriceFeedUpdate +}; +use crate::pyth::PythContract; + + +sol_storage! { + pub struct MockPythContract { + uint single_update_fee_in_wei; + uint valid_time_period; + mapping(bytes32 => StoragePriceFeed) price_feeds; + #[borrow] + PythContract pyth; + } +} + + +//#[public] +//#[inherit(PythContract)] +impl MockPythContract { + pub fn initalize(mut self, single_update_fee_in_wei: U256, valid_time_period: U256) { + self.single_update_fee_in_wei.set(single_update_fee_in_wei); + self.valid_time_period.set(valid_time_period); + } + + // pub fn guery_price_feed(self, id: FixedBytes<32>) -> Result> { + // let price_feed = self.price_feeds.getter(id).load(); + // if price_feed.id.is_empty() { + // return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound {}).into()); + // } + // Ok(price_feed.load()) + // } + pub fn price_feed_exists(self, id:FixedBytes<32>) -> bool { + self.price_feeds.getter(id).id.is_empty() == false + } + + pub fn get_valid_time_period(self) -> Uint<256, 4> { + self.valid_time_period.get() + } + + // Takes an array of encoded price feeds and stores them. + // You can create this data either by calling createPriceFeedUpdateData or + // by using web3.js or ethers abi utilities. + // @note: The updateData expected here is different from the one used in the main contract. + // In particular, the expected format is: + // [ + // abi.encode( + // PythStructs.PriceFeed( + // bytes32 id, + // PythStructs.Price price, + // PythStructs.Price emaPrice + // ), + // uint64 prevPublishTime + // ) + // ] + // pub fn update_price_feeds(self, + // update_data : Vec + // ) -> Result<(), Vec> { + // let required_fee = self.get_update_fee(update_data); + // if msg.value < required_fee { + // return Err(IPythError::InsufficientFee(InsufficientFee {}).into()); + // } + // if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); + + // for (uint i = 0; i < updateData.length; i++) { + // PythStructs.PriceFeed memory priceFeed = abi.decode( + // updateData[i], + // (PythStructs.PriceFeed) + // ); + + // uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime; + + // if (lastPublishTime < priceFeed.price.publishTime) { + // // Price information is more recent than the existing price information. + // priceFeeds[priceFeed.id] = priceFeed; + // emit PriceFeedUpdate( + // priceFeed.id, + // uint64(priceFeed.price.publishTime), + // priceFeed.price.price, + // priceFeed.price.conf + // ); + // } + // } + // } + + pub fn get_update_fee(self, + update_data: Vec + ) -> Uint<256, 4> { + self.single_update_fee_in_wei.get() * U256::from(update_data.len()) + } + + // pub fn parse_price_feed_updates_internal( + // update_data: Vec>, + // priceIds: Vec>, + // min_publish_time:u64, + // max_publish_time:u64, + // unique:bool + // ) internal returns (PythStructs.PriceFeed[] memory feeds) { + // uint requiredFee = getUpdateFee(updateData); + // if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); + + // feeds = new PythStructs.PriceFeed[](priceIds.length); + + // for (uint i = 0; i < priceIds.length; i++) { + // for (uint j = 0; j < updateData.length; j++) { + // uint64 prevPublishTime; + // (feeds[i], prevPublishTime) = abi.decode( + // updateData[j], + // (PythStructs.PriceFeed, uint64) + // ); + + // uint publishTime = feeds[i].price.publishTime; + // if (priceFeeds[feeds[i].id].price.publishTime < publishTime) { + // priceFeeds[feeds[i].id] = feeds[i]; + // emit PriceFeedUpdate( + // feeds[i].id, + // uint64(publishTime), + // feeds[i].price.price, + // feeds[i].price.conf + // ); + // } + + // if (feeds[i].id == priceIds[i]) { + // if ( + // minPublishTime <= publishTime && + // publishTime <= maxPublishTime && + // (!unique || prevPublishTime < minPublishTime) + // ) { + // break; + // } else { + // feeds[i].id = 0; + // } + // } + // } + + // if (feeds[i].id != priceIds[i]) + // revert PythErrors.PriceFeedNotFoundWithinRange(); + // } + // } + + // function parsePriceFeedUpdates( + // bytes[] calldata updateData, + // bytes32[] calldata priceIds, + // uint64 minPublishTime, + // uint64 maxPublishTime + // ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { + // return + // parsePriceFeedUpdatesInternal( + // updateData, + // priceIds, + // minPublishTime, + // maxPublishTime, + // false + // ); + // } + + // function parsePriceFeedUpdatesUnique( + // bytes[] calldata updateData, + // bytes32[] calldata priceIds, + // uint64 minPublishTime, + // uint64 maxPublishTime + // ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { + // return + // parsePriceFeedUpdatesInternal( + // updateData, + // priceIds, + // minPublishTime, + // maxPublishTime, + // true + // ); + // } + + // function createPriceFeedUpdateData( + // bytes32 id, + // int64 price, + // uint64 conf, + // int32 expo, + // int64 emaPrice, + // uint64 emaConf, + // uint64 publishTime, + // uint64 prevPublishTime + // ) public pure returns (bytes memory priceFeedData) { + // PythStructs.PriceFeed memory priceFeed; + + // priceFeed.id = id; + + // priceFeed.price.price = price; + // priceFeed.price.conf = conf; + // priceFeed.price.expo = expo; + // priceFeed.price.publishTime = publishTime; + + // priceFeed.emaPrice.price = emaPrice; + // priceFeed.emaPrice.conf = emaConf; + // priceFeed.emaPrice.expo = expo; + // priceFeed.emaPrice.publishTime = publishTime; + + // priceFeedData = abi.encode(priceFeed, prevPublishTime); + // } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index ffc3a51d84..f70d5bd955 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,7 +1,176 @@ - +use crate::pyth::errors::{IPythError, PriceFeedNotFound, StalePrice}; +use crate::pyth::solidity::{ + getEmaPriceUnsafeCall, + getPriceUnsafeCall, + updatePriceFeedsCall, + StoragePriceFeed, + StoragePrice +}; +use crate::utils::helpers::{call_helper,delegate_call_helper, CALL_RETDATA_DECODING_ERROR_MESSAGE}; +use alloc::vec::Vec; +use core::borrow::BorrowMut; +use stylus_sdk::{console,prelude::*,storage::{TopLevelStorage, StorageAddress, StorageMap},alloy_sol_types::sol}; +use alloy_primitives::{ FixedBytes,U256, Bytes, Address }; pub mod errors; -pub mod abstract_pyth; -pub mod solidity; pub mod events; -pub mod ipyth; \ No newline at end of file +//pub mod ipyth; +pub mod solidity; +pub mod mock; + +#[storage] +pub struct PythContract { + _ipyth:StorageAddress, + price_feeds:StorageMap,StoragePriceFeed>, +} + +type Price = StoragePrice; +type PriceFeed = StoragePriceFeed; + + pub fn get_ema_price_unsafe( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + id: FixedBytes<32>, + ) -> Result<(), Vec> { + let get_call = call_helper::(storage, pyth_address, (id,))?; + console!("{:?}",get_call); + // Ok(()); + // let price = Price::abi_decode(&get_call, false).map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()); + // Ok(price) + + Ok(()) + } + + + +impl PythContract { + // pub fn price_feed_exists(self, id: FixedBytes<32>) -> bool { + // self.price_feeds.getter(id).id.is_empty() + // } + + + // pub fn query_price_feed(self, id: FixedBytes<32>) -> Result> { + // let price_found = self.price_feeds.getter(id).to_price_feed(); + // if price_found.id.is_empty() { + // return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound {}).into()); + // } + // Ok(price_found) + // } + + // pub fn get_price_unsafe>( + // self, + // storage: &mut S, + // id: FixedBytes<32>, + // ) -> Result> { + // let price = call_helper::(storage, self._ipyth.get(), (id,)).unwrap(); + // Ok(price) + // } + + // pub fn get_price_no_older_than>( + // self, + // storage: &mut S, + // id: FixedBytes<32>, + // age: U256, + // ) -> Result> { + // let price = self.get_ema_price_unsafe(storage, id)?; + // if Self::_diff(U256::from(block::timestamp()), price.publish_time) > age { + // return Err(IPythError::StalePrice(StalePrice {}).into()); + // } + // Ok(price) + // } + + pub fn get_ema_price_unsafe>( + self, + storage: &mut S, + id: FixedBytes<32>, + ) -> Result<(), Vec> { + let get_call = call_helper::(storage, self._ipyth.get(), (id,))?; + console!("{:?}",get_call); + // let price = Price::abi_decode(&get_call, false).map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()); + // Ok(price) + + Ok(()) + } + + // pub fn get_ema_price_no_older_than>( + // self, + // storage: &mut S, + // id: FixedBytes<32>, + // age: U256, + // ) -> Result> { + // let price = + // call_helper::(storage, self._ipyth.get(), (id,)); + + // if Self::_diff(U256::from(block::timestamp()), price.publish_time) > age { + // return Err(IPythError::StalePrice(StalePrice {}).into()); + // } + // Ok(price) + // } + + + // fn _update_price_feeds_if_necessary>( + // self, + // storage: &mut S, + // update_data:Vec, + // price_ids: Vec>, + // publish_times: Vec + // ) -> Result<(), Vec> { + // if update_data.len() != publish_times.len() { + // return Err(IPythError::InvalidArgument(InvalidArgument{}).into()); + // } + // //let item = self; + // //for i in 0..price_ids.len() { + // // let mut i = 0; + // // loop { + // // self._check_valid_price_feed(price_ids[i]); + // // self._check_valid_query(price_ids[i], publish_times[i]); + // // } + // // { + + // //} + + // // } + // return Err(IPythError::NoFreshUpdate(NoFreshUpdate{}).into()); + // } + + + + // pub fn update_price_feeds>( + // self, + // storage: &mut S, + // update_data: Vec, + // ) -> Result<(), Vec> { + // //update_data = SolBytes::from(update_data.into()); + // delegate_call_helper::(storage, self._ipyth.get(), (update_data,))?; + // Ok(()) + // } + +} + +impl PythContract { + fn _diff(x: U256, y: U256) -> U256 { + if x > y { + return x - y; + } + y - x + } + + fn _check_valid_query( + self, + price_id: FixedBytes<32>, + publish_time: u64 + ) -> bool { + true + // return Self::query_price_feed(self,price_id).unwrap().price.publish_time < U256::from(publish_time); + } + + fn _check_valid_price_feed( + self, + price_id: FixedBytes<32>, + ) -> bool { + return self.price_feeds.getter(price_id).id.is_empty() + } +} + + + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs index ac984254fc..a203bbbb4f 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs @@ -1,8 +1,6 @@ -use core::fmt::Binary; -use alloy_primitives::I64; -use alloy_sol_types::sol; -use stylus_sdk::prelude::sol_storage; +use alloc::vec::Vec; +use stylus_sdk::{prelude::*, alloy_sol_types::{sol,}}; sol_storage! { pub struct StoragePrice { @@ -24,199 +22,93 @@ sol_storage! { } -sol! { - - struct Price { - // Price - int64 price; - // Confidence interval around the price - uint64 conf; - // Price exponent - int32 expo; - // Unix timestamp describing when the price was published - uint publishTime; - } - // PriceFeed represents a current aggregate price from pyth publisher feeds. - - struct PriceFeed { - // The price ID. - bytes32 id; - // Latest available price - Price price; - // Latest available exponentially-weighted moving average price - Price emaPrice; - } +sol! { - /// @notice Returns the price of a price feed without any sanity checks. - /// @dev This function returns the most recent price update in this contract without any recency checks. - /// This function is unsafe as the returned price update may be arbitrarily far in the past. - /// - /// Users of this function should check the `publishTime` in the price to ensure that the returned price is - /// sufficiently recent for their application. If you are considering using this function, it may be - /// safer / easier to use `getPriceNoOlderThan`. - /// @return price - please read the documentation of Price to understand how to use this safely. function getPriceUnsafe( bytes32 id - ) external view returns (Price memory price); + ) external view; - /// @notice Returns the price that is no older than `age` seconds of the current time. - /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in - /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently - /// recently. - /// @return price - please read the documentation of Price to understand how to use this safely. + function getPriceNoOlderThan( bytes32 id, uint age - ) external view returns (Price memory price); - - /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. - /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. - /// However, if the price is not recent this function returns the latest available price. - /// - /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that - /// the returned price is recent or useful for any particular application. - /// - /// Users of this function should check the `publishTime` in the price to ensure that the returned price is - /// sufficiently recent for their application. If you are considering using this function, it may be - /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. - /// @return price - please read the documentation of Price to understand how to use this safely. + ) external view; + + function getEmaPriceUnsafe( bytes32 id - ) external view returns (Price memory price); - - /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds - /// of the current time. - /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in - /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently - /// recently. - /// @return price - please read the documentation of Price to understand how to use this safely. + ) external view ; + function getEmaPriceNoOlderThan( bytes32 id, uint age - ) external view returns (Price memory price); - - /// @notice Update price feeds with given update messages. - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// Prices will be updated if they are more recent than the current stored prices. - /// The call will succeed even if the update is not the most recent. - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. - /// @param updateData Array of price update data. + ) external view ; + + function updatePriceFeeds(bytes[] calldata updateData) external payable; - /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is - /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the - /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. - /// - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// - /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime - /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have - /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. - /// Otherwise, it calls updatePriceFeeds method to update the prices. - /// - /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` function updatePriceFeedsIfNecessary( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes ) external payable; - /// @notice Returns the required fee to update an array of price updates. - /// @param updateData Array of price update data. - /// @return feeAmount The required fee in Wei. function getUpdateFee( bytes[] calldata updateData ) external view returns (uint feeAmount); - - /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published - /// within `minPublishTime` and `maxPublishTime`. - /// - /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; - /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they - /// are more recent than the current stored prices. - /// - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// - /// - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is - /// no update for any of the given `priceIds` within the given time range. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds); - - /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are - /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, - /// this method will return the first update. This method may store the price updates on-chain, if they - /// are more recent than the current stored prices. - /// - /// - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is - /// no update for any of the given `priceIds` within the given time range and uniqueness condition. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + ) external payable ; + + function parsePriceFeedUpdatesUnique( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds); + ) external payable ; function queryPriceFeed( bytes32 id - ) public view virtual returns (PriceFeed memory priceFeed); + ) public view virtual ; function priceFeedExists( bytes32 id - ) public view virtual returns (bool exists); + ) public view virtual ; - /// @notice This function is deprecated and is only kept for backward compatibility. function getValidTimePeriod() public view virtual returns (uint validTimePeriod); - } - -impl StoragePrice { - pub fn to_price(&self) ->Price { - Price { - price:self.price.get().as_i64(), - conf: self.conf.get().to(), - expo: self.expo.get().as_i32(), - publishTime: self.publish_time.get() - } - } } -impl StoragePriceFeed { - pub fn to_price_feed(&self)-> PriceFeed { - PriceFeed { - id: self.id.get(), - price: self.price.to_price(), - emaPrice: self.ema_price.to_price() - } - } -} +// impl StoragePrice { +// pub fn to_price(&self) ->Price { +// Price { +// price:self.price.get().as_i64(), +// conf: self.conf.get().to(), +// expo: self.expo.get().as_i32(), +// publish_time: self.publish_time.get() +// } +// } +// } +// impl StoragePriceFeed { +// pub fn to_price_feed(&self)-> PriceFeed { +// PriceFeed { +// id: self.id.get(), +// price: self.price.to_price(), +// emaPrice: self.ema_price.to_price() +// } +// } +// } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs index 599bc375de..caaffd015d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs @@ -1,10 +1,19 @@ -use stylus_sdk::{ - alloy_primitives::Address, - call::{call, delegate_call, static_call}, - storage::TopLevelStorage, +use { + alloc::vec::Vec, + alloy_sol_types::{ + SolCall, + SolType, + }, + stylus_sdk::{ + alloy_primitives::Address, + call::{ + call, + delegate_call, + static_call, + }, + storage::TopLevelStorage, + }, }; -use alloy_sol_types::{SolCall, SolType}; -use alloc::vec::Vec; /// The revert message when failing to decode the data @@ -29,11 +38,14 @@ pub fn delegate_call_helper( storage: &mut impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result> { +) -> Result, Vec> { + let calldata = C::new(args).abi_encode(); let res = unsafe { delegate_call(storage, address, &calldata).map_err(map_call_error)? }; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) + Ok(res) + //C//::abi_decode(data, validate) + // C::abi_decode_returns(&res, false /* validate */) + // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } /// Performs a `staticcall` to the given address, calling the function defined as a `SolCall` with the given arguments @@ -42,11 +54,12 @@ pub fn static_call_helper( storage: &impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result> { +) -> Result, Vec> { let calldata = C::new(args).abi_encode(); let res = static_call(storage, address, &calldata).map_err(map_call_error)?; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) + Ok(res) + // C::abi_decode_returns(&res, false /* validate */) + // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } /// Performs a `call` to the given address, calling the function @@ -55,9 +68,10 @@ pub fn call_helper( storage: &mut impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result> { +) -> Result, Vec> { let calldata = C::new(args).abi_encode(); let res = call(storage, address, &calldata).map_err(map_call_error)?; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) -} \ No newline at end of file + Ok(res) + // C::abi_decode_returns(&res, false /* validate */) + // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs index eadef2b964..1630fabcd1 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs @@ -1 +1 @@ -pub mod helpers; \ No newline at end of file +pub mod helpers; diff --git a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml index 80270b2646..ed43909973 100644 --- a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true edition.workspace = true [dependencies] +pyth-stylus.workspace = true alloy-primitives.workspace = true alloy-sol-types.workspace = true stylus-sdk.workspace = true @@ -15,6 +16,13 @@ mini-alloc.workspace = true stylus-proc.workspace = true +[lib] +crate-type = ["lib", "cdylib"] -[lints] -workspace = true +[features] +export-abi = ["stylus-sdk/export-abi"] +debug = ["stylus-sdk/debug"] + +[[bin]] +name = "mock" +path = "src/main.rs" diff --git a/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs b/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs new file mode 100644 index 0000000000..7b6d4ca4b3 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs @@ -0,0 +1,78 @@ +//! Example on how to interact with a deployed `stylus-hello-world` contract using defaults. +//! This example uses ethers-rs to instantiate the contract using a Solidity ABI. +//! Then, it attempts to check the current counter value, increment it via a tx, +//! and check the value again. The deployed contract is fully written in Rust and compiled to WASM +//! but with Stylus, it is accessible just as a normal Solidity smart contract is via an ABI. + +use ethers::{ + middleware::SignerMiddleware, + prelude::abigen, + providers::{Http, Middleware, Provider}, + signers::{LocalWallet, Signer}, + types::Address, +}; +use dotenv::dotenv; +use eyre::eyre; +use std::io::{BufRead, BufReader}; +use std::str::FromStr; +use std::sync::Arc; + +/// Your private key file path. +const PRIV_KEY_PATH: &str = "PRIV_KEY_PATH"; + +/// Stylus RPC endpoint url. +const RPC_URL: &str = "RPC_URL"; + +/// Deployed pragram address. +const STYLUS_CONTRACT_ADDRESS: &str = "STYLUS_CONTRACT_ADDRESS"; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenv().ok(); + let priv_key_path = + std::env::var(PRIV_KEY_PATH).map_err(|_| eyre!("No {} env var set", PRIV_KEY_PATH))?; + let rpc_url = std::env::var(RPC_URL).map_err(|_| eyre!("No {} env var set", RPC_URL))?; + let contract_address = std::env::var(STYLUS_CONTRACT_ADDRESS) + .map_err(|_| eyre!("No {} env var set", STYLUS_CONTRACT_ADDRESS))?; + + abigen!( + PythUse, + r#"[ + function initialize(address ipth_address) external; + function number(bytes32 id) external; + ]"# + ); + + let provider = Provider::::try_from(rpc_url)?; + let address: Address = contract_address.parse()?; + + let privkey = read_secret_from_file(&priv_key_path)?; + let wallet = LocalWallet::from_str(&privkey)?; + let chain_id = provider.get_chainid().await?.as_u64(); + let client = Arc::new(SignerMiddleware::new( + provider, + wallet.clone().with_chain_id(chain_id), + )); + + // let counter = Counter::new(address, client); + // let num = counter.number().call().await; + // println!("Counter number value = {:?}", num); + + // let pending = counter.increment(); + // if let Some(receipt) = pending.send().await?.await? { + // println!("Receipt = {:?}", receipt); + // } + // println!("Successfully incremented counter via a tx"); + + // let num = counter.number().call().await; + // println!("New counter number value = {:?}", num); + // Ok(()) +} + +fn read_secret_from_file(fpath: &str) -> eyre::Result { + let f = std::fs::File::open(fpath)?; + let mut buf_reader = BufReader::new(f); + let mut secret = String::new(); + buf_reader.read_line(&mut secret)?; + Ok(secret.trim().to_string()) +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/mock/privateKey.txt b/target_chains/ethereum/sdk/stylus/mock/privateKey.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs index e69de29bb2..cffaba510a 100644 --- a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs @@ -0,0 +1,44 @@ +#![cfg_attr(not(feature = "export-abi"), no_main)] +extern crate alloc; + +use core::borrow; +use std::fmt::Result; + +/// Import items from the SDK. The prelude contains common traits and macros. +use stylus_sdk::{ prelude::*, stylus_proc::entrypoint, alloy_sol_types::sol}; +use pyth_stylus::pyth::{PythContract, get_ema_price_unsafe}; +use alloy_primitives::{ FixedBytes, Address }; + +// Define some persistent storage using the Solidity ABI. +// `Counter` will be the entrypoint. + +sol!{ + error AlreadyInitialized(); +} + + +#[derive(SolidityError)] +pub enum PythUseError { + + AlreadyInitialized(AlreadyInitialized), +} + +sol_storage! { + #[entrypoint] + pub struct PythUse { + address pyth; + } +} + +/// Declare that `Counter` is a contract with the following external methods. +#[public] +impl PythUse { + pub fn initialize(&mut self, ipth_address: Address) { + self.pyth.set(ipth_address); + } + /// Gets the number from storage. + pub fn number(&mut self, id:FixedBytes<32>) { + let _ = get_ema_price_unsafe(self, self.pyth.get(), id); + + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/mock/src/main.rs b/target_chains/ethereum/sdk/stylus/mock/src/main.rs index e7a11a969c..7c6d15313f 100644 --- a/target_chains/ethereum/sdk/stylus/mock/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/mock/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(not(feature = "export-abi"), no_main)] + +#[cfg(feature = "export-abi")] fn main() { - println!("Hello, world!"); -} + mock::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;"); +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/netlify.toml b/target_chains/ethereum/sdk/stylus/netlify.toml new file mode 100644 index 0000000000..581c663645 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/netlify.toml @@ -0,0 +1,4 @@ +[build] +base = "docs/" +command = "npm run docs" +publish = "build/site" diff --git a/target_chains/ethereum/sdk/stylus/rustfmt.toml b/target_chains/ethereum/sdk/stylus/rustfmt.toml new file mode 100644 index 0000000000..769661dee7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/rustfmt.toml @@ -0,0 +1,8 @@ +max_width = 80 +format_macro_matchers = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +reorder_impl_items = true +use_field_init_shorthand = true +use_small_heuristics = "Max" +wrap_comments = true From 96dda48082013a122003d73691ff7c15763af2a2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 3 Oct 2024 06:16:32 +0000 Subject: [PATCH 019/160] chore: config changes --- .../ethereum/sdk/stylus/.cargo/config.toml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/.cargo/config.toml b/target_chains/ethereum/sdk/stylus/.cargo/config.toml index 77f73804f6..8c46df4b48 100644 --- a/target_chains/ethereum/sdk/stylus/.cargo/config.toml +++ b/target_chains/ethereum/sdk/stylus/.cargo/config.toml @@ -1,8 +1,16 @@ [target.wasm32-unknown-unknown] -rustflags = ["-C", "link-arg=-zstack-size=8192"] +rustflags = [ + "-C", "link-arg=-zstack-size=32768", +] [target.aarch64-apple-darwin] -rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] [target.x86_64-apple-darwin] -rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] \ No newline at end of file +rustflags = [ +"-C", "link-arg=-undefined", +"-C", "link-arg=dynamic_lookup", +] From dc847cd3551e03297c501659dd0af5dc6e03ef52 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 3 Oct 2024 06:17:11 +0000 Subject: [PATCH 020/160] chore: forge init --- .../pyth-solidity/.github/workflows/test.yml | 45 +++++++++++++ .../sdk/stylus/pyth-solidity/.gitignore | 14 ++++ .../sdk/stylus/pyth-solidity/README.md | 66 +++++++++++++++++++ .../sdk/stylus/pyth-solidity/foundry.toml | 6 ++ .../stylus/pyth-solidity/script/Counter.s.sol | 19 ++++++ .../sdk/stylus/pyth-solidity/src/Counter.sol | 14 ++++ .../stylus/pyth-solidity/test/Counter.t.sol | 24 +++++++ 7 files changed, 188 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/README.md create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml new file mode 100644 index 0000000000..762a2966f7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore b/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore new file mode 100644 index 0000000000..85198aaa55 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md b/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md new file mode 100644 index 0000000000..9265b45584 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml b/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml new file mode 100644 index 0000000000..25b918f9c9 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol new file mode 100644 index 0000000000..cdc1fe9a1b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterScript is Script { + Counter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = new Counter(); + + vm.stopBroadcast(); + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol new file mode 100644 index 0000000000..aded7997b0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol new file mode 100644 index 0000000000..54b724f7ae --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 6588fe6d9fe3147b6b1aa99eb3c847d338d1a557 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 3 Oct 2024 06:17:17 +0000 Subject: [PATCH 021/160] forge install: forge-std v1.9.3 --- .gitmodules | 3 +++ target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..f0585d7f91 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std"] + path = target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std new file mode 160000 index 0000000000..8f24d6b04c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa From da11d45b76a6326c6165aef6335d290ab77be036 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 3 Oct 2024 09:15:27 +0000 Subject: [PATCH 022/160] mock pyth : created mock solidity pyth contract --- .../sdk/stylus/pyth-solidity/.env.example | 1 + .../stylus/pyth-solidity/package-lock.json | 28 ++++++++++++ .../sdk/stylus/pyth-solidity/package.json | 19 ++++++++ .../sdk/stylus/pyth-solidity/remappings.txt | 1 + .../stylus/pyth-solidity/script/Counter.s.sol | 4 -- .../sdk/stylus/pyth-solidity/src/Counter.sol | 14 ------ .../sdk/stylus/pyth-solidity/src/MockPyth.sol | 44 +++++++++++++++++++ .../stylus/pyth-solidity/test/Counter.t.sol | 16 +------ 8 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/package.json create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol create mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example b/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example new file mode 100644 index 0000000000..937ce56fd5 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example @@ -0,0 +1 @@ +RPC="http://localhost:8545" \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json b/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json new file mode 100644 index 0000000000..213e39f20b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "pyth-solidity", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "pyth-solidity", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } + }, + "node_modules/@pythnetwork/pyth-sdk-solidity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", + "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" + } + }, + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", + "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" + } + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json new file mode 100644 index 0000000000..372dfd8609 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json @@ -0,0 +1,19 @@ +{ + "name": "pyth-solidity", + "version": "1.0.0", + "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", + "main": "index.js", + "directories": { + "lib": "lib", + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt b/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt new file mode 100644 index 0000000000..939d22cd46 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt @@ -0,0 +1 @@ +@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol index cdc1fe9a1b..9d73e1fc3b 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol @@ -2,18 +2,14 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; contract CounterScript is Script { - Counter public counter; function setUp() public {} function run() public { vm.startBroadcast(); - counter = new Counter(); - vm.stopBroadcast(); } } diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol deleted file mode 100644 index aded7997b0..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol new file mode 100644 index 0000000000..a09825d7de --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; + +contract MockPythSample is MockPyth { + uint randNonce = 0; + string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; + bytes32[] priceIds = new bytes32[](tickers.length); + + constructor( + uint validTimePeriod, uint singleUpdateFeeInWei + ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { + getPriceIds(); + for (uint i = 0; i< tickers.length; i++) { + uint randomNumber = randNumber(priceIds[i],10); + int64 price = int64(uint64(randomNumber + randNumber(priceIds[i],2))); + uint64 conf = uint64(randomNumber+ randNumber(priceIds[i],3)); + int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40))); + int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5))); + uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6)); + createPriceFeedUpdateData(priceIds[i],price, conf, expo,emaPrice, emaConf, uint64(block.timestamp),0); + } + } + + function getPriceIds() internal { + for (uint i = 0; i < tickers.length; i++) { + bytes memory tempEmptyStringTest = bytes(tickers[i]); + if (tempEmptyStringTest.length == 0) { + priceIds[i] = bytes32(0); + } + else { + priceIds[i] = keccak256(abi.encodePacked(tickers[i])); + } + } + } + + function randNumber(bytes32 ticker, uint salt) internal returns(uint) + { + // increase nonce + randNonce++; + return uint(keccak256(abi.encodePacked(block.timestamp, ticker,msg.sender,randNonce, salt))) % randNonce; + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol index 54b724f7ae..19bcf7694e 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol @@ -2,23 +2,9 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; -contract CounterTest is Test { - Counter public counter; +contract MockPythSampleTest is Test { function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); } } From 5cc9626cabcdb33ffb7a61a06956d20bbef97522 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 4 Oct 2024 09:03:32 +0000 Subject: [PATCH 023/160] chore: Renamed MockPyth Scripts --- .../pyth-solidity/script/{Counter.s.sol => MockPyth.s.sol} | 2 +- .../stylus/pyth-solidity/test/{Counter.t.sol => MockPyth.t.sol} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename target_chains/ethereum/sdk/stylus/pyth-solidity/script/{Counter.s.sol => MockPyth.s.sol} (87%) rename target_chains/ethereum/sdk/stylus/pyth-solidity/test/{Counter.t.sol => MockPyth.t.sol} (80%) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol similarity index 87% rename from target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol rename to target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index 9d73e1fc3b..c3ff5f40c8 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/Counter.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; -contract CounterScript is Script { +contract MockPythScript is Script { function setUp() public {} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol similarity index 80% rename from target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol rename to target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol index 19bcf7694e..1ce20d6d2c 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/Counter.t.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; -contract MockPythSampleTest is Test { +contract MockPythTest is Test { function setUp() public { } From da154c06576662d048ec9679e8fb9513013a4465 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 5 Oct 2024 23:04:21 +0000 Subject: [PATCH 024/160] chore: started testing --- target_chains/ethereum/sdk/stylus/Cargo.lock | 37 +- target_chains/ethereum/sdk/stylus/Cargo.toml | 14 + .../ethereum/sdk/stylus/contracts/Cargo.toml | 1 + .../ethereum/sdk/stylus/contracts/src/lib.rs | 52 +- .../sdk/stylus/contracts/src/pyth/mod.rs | 104 +-- .../ethereum/sdk/stylus/lib/crypto/Cargo.toml | 23 + .../ethereum/sdk/stylus/lib/crypto/README.md | 34 + .../sdk/stylus/lib/crypto/src/hash.rs | 192 +++++ .../sdk/stylus/lib/crypto/src/keccak.rs | 49 ++ .../ethereum/sdk/stylus/lib/crypto/src/lib.rs | 28 + .../sdk/stylus/lib/crypto/src/merkle.rs | 664 ++++++++++++++++++ .../sdk/stylus/lib/e2e-proc/Cargo.toml | 20 + .../sdk/stylus/lib/e2e-proc/README.md | 5 + .../sdk/stylus/lib/e2e-proc/src/lib.rs | 21 + .../sdk/stylus/lib/e2e-proc/src/test.rs | 47 ++ .../ethereum/sdk/stylus/lib/e2e/Cargo.toml | 22 + .../ethereum/sdk/stylus/lib/e2e/README.md | 153 ++++ .../sdk/stylus/lib/e2e/src/account.rs | 119 ++++ .../ethereum/sdk/stylus/lib/e2e/src/deploy.rs | 68 ++ .../sdk/stylus/lib/e2e/src/environment.rs | 37 + .../ethereum/sdk/stylus/lib/e2e/src/error.rs | 116 +++ .../ethereum/sdk/stylus/lib/e2e/src/event.rs | 23 + .../ethereum/sdk/stylus/lib/e2e/src/lib.rs | 92 +++ .../sdk/stylus/lib/e2e/src/project.rs | 105 +++ .../sdk/stylus/lib/e2e/src/receipt.rs | 17 + .../ethereum/sdk/stylus/lib/e2e/src/system.rs | 85 +++ .../ethereum/sdk/stylus/mock/Cargo.toml | 4 - .../sdk/stylus/mock/example/pythtest.rs | 78 -- .../ethereum/sdk/stylus/mock/src/lib.rs | 4 +- .../ethereum/sdk/stylus/mock/src/main.rs | 6 - 30 files changed, 2074 insertions(+), 146 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/README.md create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/README.md create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs delete mode 100644 target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs delete mode 100644 target_chains/ethereum/sdk/stylus/mock/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index bcf75a6677..d4968f1c5b 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" dependencies = [ "const-hex", "dunce", @@ -800,6 +800,30 @@ dependencies = [ "stylus-sdk", ] +[[package]] +name = "motsu" +version = "0.1.0-rc" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cff6390ad19dea5f6bf7011af9560c3dc5f9162fa4b1b33c0d24df18a2c7fcb" +dependencies = [ + "const-hex", + "motsu-proc", + "once_cell", + "stylus-sdk", + "tiny-keccak", +] + +[[package]] +name = "motsu-proc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -986,6 +1010,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "mini-alloc 0.4.2", + "motsu", "stylus-sdk", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index eda7726318..b09a9173ea 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -36,9 +36,23 @@ stylus-sdk = "0.6.0" stylus-proc = "0.6.0" hex = "0.4.3" dotenv = "0.15.0" +const-hex = { version = "1.11.1", default-features = false } +eyre = "0.6.8" +keccak-const = "0.2.0" +koba = "0.2.0" +once_cell = "1.19.0" +rand = "0.8.5" +regex = "1.10.4" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } +tokio = { version = "1.12.0", features = ["full"] } + +# motsu test +motsu = "=0.1.0-rc" # members pyth-stylus = { path = "contracts" } +e2e = { path = "lib/e2e" } +e2e-proc = {path = "lib/e2e-proc"} [profile.release] codegen-units = 1 diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index c8bafc6c4a..580dd06c20 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -12,6 +12,7 @@ alloy-primitives.workspace = true alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true +motsu = "=0.1.0-rc" [features] diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 2db5c5f00c..9c312c6bd7 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -1,14 +1,58 @@ +/*! +# OpenZeppelin Contracts for Stylus + +A library for secure smart contract development written in Rust for +[Arbitrum Stylus](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). +This library offers common smart contract primitives and affordances that take +advantage of the nature of Stylus. + +> This project is still in a very early and experimental phase. It has never +> been audited nor thoroughly reviewed for security vulnerabilities. Do not use +> in production. + +## Usage + +To start using it, add `openzeppelin-stylus` to your `Cargo.toml`, or simply run +`cargo add openzeppelin-stylus`. + +```toml +[dependencies] +openzeppelin-stylus = "x.x.x" +``` + +We recommend pinning to a specific version -- expect rapid iteration. + +Once defined as a dependency, use one of our pre-defined implementations by +importing them: + +```ignore +use openzeppelin_stylus::token::erc20::Erc20; + +sol_storage! { + #[entrypoint] + struct MyContract { + #[borrow] + Erc20 erc20; + } +} + +#[external] +#[inherit(Erc20)] +impl MyContract { } +``` +*/ + + #![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; -// #[global_allocator] -// static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod utils; -//pub mod structs; pub mod pyth; -// pub mod mock; + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index f70d5bd955..3e974da2e3 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,14 +1,13 @@ use crate::pyth::errors::{IPythError, PriceFeedNotFound, StalePrice}; use crate::pyth::solidity::{ getEmaPriceUnsafeCall, - getPriceUnsafeCall, - updatePriceFeedsCall, + // getPriceUnsafeCall, + // updatePriceFeedsCall, StoragePriceFeed, StoragePrice }; use crate::utils::helpers::{call_helper,delegate_call_helper, CALL_RETDATA_DECODING_ERROR_MESSAGE}; use alloc::vec::Vec; -use core::borrow::BorrowMut; use stylus_sdk::{console,prelude::*,storage::{TopLevelStorage, StorageAddress, StorageMap},alloy_sol_types::sol}; use alloy_primitives::{ FixedBytes,U256, Bytes, Address }; @@ -18,32 +17,32 @@ pub mod events; pub mod solidity; pub mod mock; -#[storage] -pub struct PythContract { - _ipyth:StorageAddress, - price_feeds:StorageMap,StoragePriceFeed>, + +sol_storage! { + #[entrypoint] + pub struct PythContract { + address _ipyth; + } } type Price = StoragePrice; type PriceFeed = StoragePriceFeed; - pub fn get_ema_price_unsafe( - storage: &mut impl TopLevelStorage, - pyth_address: Address, - id: FixedBytes<32>, - ) -> Result<(), Vec> { - let get_call = call_helper::(storage, pyth_address, (id,))?; - console!("{:?}",get_call); +pub fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result<(), Vec> { + let _get_call = call_helper::(storage, pyth_address, (id,))?; + console!("{:?}",_get_call); // Ok(()); // let price = Price::abi_decode(&get_call, false).map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()); // Ok(price) Ok(()) - } - - +} +#[public] impl PythContract { + pub fn initialize(&mut self, ipth_address: Address) { + self._ipyth.set(ipth_address); + } // pub fn price_feed_exists(self, id: FixedBytes<32>) -> bool { // self.price_feeds.getter(id).id.is_empty() // } @@ -79,16 +78,12 @@ impl PythContract { // Ok(price) // } - pub fn get_ema_price_unsafe>( - self, - storage: &mut S, + pub fn get_ema_price_unsafe_test( + &mut self, id: FixedBytes<32>, ) -> Result<(), Vec> { - let get_call = call_helper::(storage, self._ipyth.get(), (id,))?; - console!("{:?}",get_call); - // let price = Price::abi_decode(&get_call, false).map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()); - // Ok(price) - + let data = get_ema_price_unsafe(self, self._ipyth.get(), id); + console!("{:?}",data); Ok(()) } @@ -148,29 +143,48 @@ impl PythContract { } impl PythContract { - fn _diff(x: U256, y: U256) -> U256 { - if x > y { - return x - y; - } - y - x - } + // fn _diff(x: U256, y: U256) -> U256 { + // if x > y { + // return x - y; + // } + // y - x + // } - fn _check_valid_query( - self, - price_id: FixedBytes<32>, - publish_time: u64 - ) -> bool { - true - // return Self::query_price_feed(self,price_id).unwrap().price.publish_time < U256::from(publish_time); - } + // fn _check_valid_query( + // self, + // price_id: FixedBytes<32>, + // publish_time: u64 + // ) -> bool { + // true + // // return Self::query_price_feed(self,price_id).unwrap().price.publish_time < U256::from(publish_time); + // } - fn _check_valid_price_feed( - self, - price_id: FixedBytes<32>, - ) -> bool { - return self.price_feeds.getter(price_id).id.is_empty() - } + // fn _check_valid_price_feed( + // self, + // price_id: FixedBytes<32>, + // ) -> bool { + // return self.price_feeds.getter(price_id).id.is_empty() + // } } +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, uint, Address, U256}; + use stylus_sdk::msg; + use crate::pyth::PythContract; + + #[motsu::test] + fn check_invalid_ipyth_address(contract: PythContract) { + let unsafe_call = contract.get_ema_price_unsafe(Address::ZERO); + // assert_eq!(U256::ZERO, balance); + + // let owner = msg::sender(); + // let one = uint!(1_U256); + // contract._balances.setter(owner).set(one); + // let balance = contract.balance_of(owner); + // assert_eq!(one, balance); + } + +} diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml new file mode 100644 index 0000000000..cf119fc923 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "openzeppelin-crypto" +categories = ["cryptography", "algorithms", "no-std", "wasm"] +description = "Cryptography Utilities" +edition.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version = "0.1.0-rc" + +[dependencies] +mini-alloc.workspace = true +tiny-keccak.workspace = true + +[dev-dependencies] +hex-literal = "0.4.1" +rand.workspace = true + +[features] +std = [] + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/README.md b/target_chains/ethereum/sdk/stylus/lib/crypto/README.md new file mode 100644 index 0000000000..081bdb1927 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/README.md @@ -0,0 +1,34 @@ +# Cryptographic Utilities + +Common cryptographic procedures for a blockchain environment. + +> [!WARNING] +> Note that `crypto` is still `0.*.*`, so breaking changes +> [may occur at any time](https://semver.org/#spec-item-4). If you must depend +> on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. + +## Verifying Merkle Proofs + +[`merkle.rs`](./src/merkle.rs) provides: + +- A `verify` function which can prove that some value is part of a + [Merkle tree]. +- A `verify_multi_proof` function which can prove multiple values are part of a + [Merkle tree]. + +[Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree + +## Feature Flags + +This crate exposes its modules behind feature gates to ensure the bare minimum +is included in consumer codebases. You can check the current feature flags in +the [Cargo.toml](./Cargo.toml) file. + +## Security + +> [!WARNING] +> This project is still in a very early and experimental phase. It has never +> been audited nor thoroughly reviewed for security vulnerabilities. Do not use +> in production. + +Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs new file mode 100644 index 0000000000..65a641108c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs @@ -0,0 +1,192 @@ +//! Generic hashing support. +//! +//! This module provides a generic way to compute the [hash] of a value. It is +//! intended to be used as a replacement for [`core::hash`], which we can't use +//! because [`core::hash::Hasher::finish`] returns a `u64`. +//! +//! [hash]: https://en.wikipedia.org/wiki/Hash_function + +/// A hashable type. +/// +/// Types implementing `Hash` are able to be [`Hash::hash`]ed with an instance +/// of [`Hasher`]. +pub trait Hash { + /// Feeds this value into the given [`Hasher`]. + fn hash(&self, state: &mut H); +} + +/// A trait for hashing an arbitrary stream of bytes. +/// +/// Instances of `Hasher` usually represent state that is changed while hashing +/// data. +/// +/// `Hasher` provides a fairly basic interface for retrieving the generated hash +/// (with [`Hasher::finalize`]), and absorbing an arbitrary number of bytes +/// (with [`Hasher::update`]). Most of the time, [`Hasher`] instances are used +/// in conjunction with the [`Hash`] trait. +pub trait Hasher { + /// The output type of this hasher. + /// + /// For [`core::hash`] types, it's `u64`. For [`tiny_keccak`], it's `[u8]`. + /// For this crate, it's `[u8; 32]`. + type Output; + + /// Absorb additional input. Can be called multiple times. + fn update(&mut self, input: impl AsRef<[u8]>); + + /// Output the hashing algorithm state. + fn finalize(self) -> Self::Output; +} + +/// A trait for creating instances of [`Hasher`]. +/// +/// A `BuildHasher` is typically used (e.g., by [`HashMap`]) to create +/// [`Hasher`]s for each key such that they are hashed independently of one +/// another, since [`Hasher`]s contain state. +/// +/// For each instance of `BuildHasher`, the [`Hasher`]s created by +/// [`build_hasher`] should be identical. That is, if the same stream of bytes +/// is fed into each hasher, the same output will also be generated. +/// +/// # Examples +/// +/// ```rust +/// use openzeppelin_crypto::KeccakBuilder; +/// use openzeppelin_crypto::hash::{BuildHasher, Hash, Hasher}; +/// +/// let b = KeccakBuilder; +/// let mut hasher_1 = b.build_hasher(); +/// let mut hasher_2 = b.build_hasher(); +/// +/// hasher_1.update([1]); +/// hasher_2.update([1]); +/// +/// assert_eq!(hasher_1.finalize(), hasher_2.finalize()); +/// ``` +/// +/// [`build_hasher`]: BuildHasher::build_hasher +/// [`HashMap`]: ../../std/collections/struct.HashMap.html +pub trait BuildHasher { + /// Type of the hasher that will be created. + type Hasher: Hasher; + + /// Creates a new hasher. + /// + /// Each call to `build_hasher` on the same instance should produce + /// identical [`Hasher`]s. + /// + /// # Examples + /// + /// ```rust + /// use openzeppelin_crypto::KeccakBuilder; + /// use openzeppelin_crypto::hash::BuildHasher; + /// + /// let b = KeccakBuilder; + /// let hasher = b.build_hasher(); + /// ``` + fn build_hasher(&self) -> Self::Hasher; + + /// Calculates the hash of a single value. + /// + /// This is intended as a convenience for code which *consumes* hashes, such + /// as the implementation of a hash table or in unit tests that check + /// whether a custom [`Hash`] implementation behaves as expected. + /// + /// This must not be used in any code which *creates* hashes, such as in an + /// implementation of [`Hash`]. The way to create a combined hash of + /// multiple values is to call [`Hash::hash`] multiple times using the same + /// [`Hasher`], not to call this method repeatedly and combine the results. + /// + /// # Examples + /// + /// ```rust + /// use openzeppelin_crypto::KeccakBuilder; + /// use openzeppelin_crypto::hash::{BuildHasher, Hash}; + /// + /// let b = KeccakBuilder; + /// let hash_1 = b.hash_one([0u8; 32]); + /// let hash_2 = b.hash_one([0u8; 32]); + /// assert_eq!(hash_1, hash_2); + /// + /// let hash_1 = b.hash_one([1u8; 32]); + /// assert_ne!(hash_1, hash_2); + /// ``` + fn hash_one( + &self, + h: Hashable, + ) -> ::Output + where + Hashable: Hash, + Self: Sized, + Self::Hasher: Hasher, + { + let mut hasher = self.build_hasher(); + h.hash(&mut hasher); + hasher.finalize() + } +} + +/// Hash the pair `(a, b)` with `state`. +#[allow(clippy::module_name_repetitions)] +#[inline] +pub fn hash_pair(a: &H, b: &H, mut state: S) -> S::Output +where + H: Hash + ?Sized, + S: Hasher, +{ + a.hash(&mut state); + b.hash(&mut state); + state.finalize() +} + +/// Sort the pair `(a, b)` and hash the result with `state`. Frequently used +/// when working with merkle proofs. +#[inline] +pub fn commutative_hash_pair(mut a: H, mut b: H, state: S) -> S::Output +where + H: Hash + PartialOrd, + S: Hasher, +{ + if a > b { + core::mem::swap(&mut a, &mut b); + } + + hash_pair(&a, &b, state) +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::{commutative_hash_pair, hash_pair, BuildHasher, Hash, Hasher}; + use crate::KeccakBuilder; + + impl Hash for &[u8] { + fn hash(&self, state: &mut H) { + state.update(self); + } + } + + #[test] + fn hashes_pairs() { + let builder = KeccakBuilder; + let a = [1u8].as_slice(); + let b = [2u8].as_slice(); + + let r1 = hash_pair(&a, &b, builder.build_hasher()); + let r2 = hash_pair(&a, &b, builder.build_hasher()); + assert_eq!(r1, r2); + + let r3 = hash_pair(&b, &a, builder.build_hasher()); + assert_ne!(r1, r3); + } + + #[test] + fn commutatively_hashes_pairs() { + let builder = KeccakBuilder; + let a = [1u8].as_slice(); + let b = [2u8].as_slice(); + + let r1 = commutative_hash_pair(a, b, builder.build_hasher()); + let r2 = commutative_hash_pair(b, a, builder.build_hasher()); + assert_eq!(r1, r2); + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs new file mode 100644 index 0000000000..136d4afb74 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs @@ -0,0 +1,49 @@ +//! An interface to the default hashing algorithm used in this library's [merkle +//! proofs][crate]. +use tiny_keccak::{Hasher as TinyHasher, Keccak}; + +use crate::hash::{BuildHasher, Hash, Hasher}; + +/// The default [`Hasher`] builder used in this library's [merkle +/// proofs][crate]. +/// +/// It instantiates a [`Keccak256`] hasher. +#[allow(clippy::module_name_repetitions)] +pub struct KeccakBuilder; + +impl BuildHasher for KeccakBuilder { + type Hasher = Keccak256; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + Keccak256(Keccak::v256()) + } +} + +/// The default [`Hasher`] used in this library's [merkle proofs][crate]. +/// +/// The underlying implementation is guaranteed to match that of the +/// `keccak256` algorithm, commonly used in Ethereum. +#[allow(clippy::module_name_repetitions)] +pub struct Keccak256(Keccak); + +impl Hasher for Keccak256 { + type Output = [u8; 32]; + + fn update(&mut self, input: impl AsRef<[u8]>) { + self.0.update(input.as_ref()); + } + + fn finalize(self) -> Self::Output { + let mut buffer = [0u8; 32]; + self.0.finalize(&mut buffer); + buffer + } +} + +impl Hash for [u8; 32] { + #[inline] + fn hash(&self, state: &mut H) { + state.update(self); + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs new file mode 100644 index 0000000000..8c7fc0127b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs @@ -0,0 +1,28 @@ +/*! +Common cryptographic procedures for a blockchain environment. + +> Note that `crypto` is still `0.*.*`, so breaking changes +> [may occur at any time](https://semver.org/#spec-item-4). If you must depend +> on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. + +## Verifying Merkle Proofs + +[`merkle.rs`](./src/merkle.rs) provides: + +- A `verify` function which can prove that some value is part of a + [Merkle tree]. +- A `verify_multi_proof` function which can prove multiple values are part of a + [Merkle tree]. + +[Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree + +*/ + +#![cfg_attr(not(feature = "std"), no_std, no_main)] +extern crate alloc; + +pub mod hash; +pub mod merkle; + +pub mod keccak; +pub use keccak::KeccakBuilder; diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs new file mode 100644 index 0000000000..74063f575a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs @@ -0,0 +1,664 @@ +//! This module deals with verification of Merkle Tree proofs. +//! +//! The tree and the proofs can be generated using `OpenZeppelin`'s +//! [merkle tree library](https://github.com/OpenZeppelin/merkle-tree). You will +//! find a quickstart guide in its README. +//! +//! WARNING: You should avoid using leaf values that are 64 bytes long +//! prior to hashing, or use a hash function other than keccak256 for +//! hashing leaves. This is because the concatenation of a sorted pair +//! of internal nodes in the Merkle tree could be reinterpreted as a +//! leaf value. `OpenZeppelin`'s JavaScript library generates Merkle trees +//! that are safe against this attack out of the box. +use alloc::vec::Vec; +use core::marker::PhantomData; + +use crate::{ + hash::{commutative_hash_pair, BuildHasher, Hasher}, + KeccakBuilder, +}; + +type Bytes32 = [u8; 32]; + +/// Verify merkle proofs. +pub struct Verifier(PhantomData) +where + B: BuildHasher; + +impl Verifier { + /// Verify that `leaf` is part of a Merkle tree defined by `root` by using + /// `proof` and the default `keccak256` hashing algorithm. + /// + /// A new root is rebuilt by traversing up the Merkle tree. The `proof` + /// provided must contain sibling hashes on the branch starting from the + /// leaf to the root of the tree. Each pair of leaves and each pair of + /// pre-images are assumed to be sorted. + /// + /// A `proof` is valid if and only if the rebuilt hash matches the root + /// of the tree. + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaf` - The leaf of the merkle tree to proof, in bytes. + /// + /// # Examples + /// + /// ``` + /// use openzeppelin_crypto::merkle::Verifier; + /// use hex_literal::hex; + /// + /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// + /// let verification = Verifier::verify(&[proof], root, leaf); + /// assert!(!verification); + /// ``` + #[must_use] + pub fn verify(proof: &[Bytes32], root: Bytes32, leaf: Bytes32) -> bool { + Verifier::verify_with_builder(proof, root, leaf, &KeccakBuilder) + } + + /// Verify multiple `leaves` can be simultaneously proven to be a part of + /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` + /// and a `hasher`. + /// + /// The `proof` must contain the sibling hashes one would need to rebuild + /// the root starting from `leaves`. `proof_flags` represents whether a + /// hash must be computed using a `proof` member. A new root is rebuilt by + /// starting from the `leaves` and traversing up the Merkle tree. + /// + /// The procedure incrementally reconstructs all inner nodes by combining + /// a leaf/inner node with either another leaf/inner node or a `proof` + /// sibling node, depending on each proof flag being true or false + /// respectively, i.e., the `i`-th hash must be computed using the proof if + /// `proof_flags[i] == false`. + /// + /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, + /// it is sufficient to ensure that: + /// - The tree is complete (but not necessarily perfect). + /// - The leaves to be proven are in the opposite order they appear in the + /// tree (i.e., as seen from right to left starting at the deepest layer + /// and continuing at the next layer). + /// + /// NOTE: This implementation is *not* equivalent to it's Solidity + /// counterpart. In Rust, access to uninitialized memory panics, which + /// means we don't need to check that the whole proof array has been + /// processed. Both implementations will revert for the same inputs, but + /// for different reasons. See + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `proof_flags` - A slice of booleans that determine whether to hash + /// leaves or the proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaves` - A slice of hashes that constitute the leaves of the merkle + /// tree to be proven, each leaf in bytes. + /// + /// # Errors + /// + /// Will return `Err` if the arguments are well-formed, but invalid. + /// + /// # Panics + /// + /// Will panic with an out-of-bounds error if the proof is malicious. See + /// + /// + /// # Examples + /// + /// ```rust + /// use openzeppelin_crypto::merkle::Verifier; + /// use hex_literal::hex; + /// + /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); + /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), + /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), + /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; + /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), + /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; + /// + /// let proof_flags = [false, true, false, true]; + /// + /// let verification = + /// Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + /// assert!(verification.unwrap()); + /// ``` + pub fn verify_multi_proof( + proof: &[Bytes32], + proof_flags: &[bool], + root: Bytes32, + leaves: &[Bytes32], + ) -> Result { + Verifier::verify_multi_proof_with_builder( + proof, + proof_flags, + root, + leaves, + &KeccakBuilder, + ) + } +} + +impl Verifier +where + B: BuildHasher, + B::Hasher: Hasher, +{ + /// Verify that `leaf` is part of a Merkle tree defined by `root` by using + /// `proof` and a custom hashing algorithm defined by `builder`. See + /// [`BuildHasher`] for more information on how to construct a builder. + /// + /// WARNING: This is a lower-level function. For most use cases, + /// [`Verifier::verify`], which uses `keccak256` as a hashing algorithm, + /// should be enough. Using other hashing algorithm may have unexpected + /// results. + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaf` - The leaf of the merkle tree to proof, in bytes. + /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. + /// + /// # Examples + /// + /// ``` + /// use openzeppelin_crypto::{merkle::Verifier, KeccakBuilder}; + /// use hex_literal::hex; + /// + /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// + /// let verification = Verifier::verify_with_builder(&[proof], root, leaf, &KeccakBuilder); + /// assert!(!verification); + /// ``` + pub fn verify_with_builder( + proof: &[Bytes32], + root: Bytes32, + mut leaf: Bytes32, + builder: &B, + ) -> bool { + for &hash in proof { + leaf = commutative_hash_pair(leaf, hash, builder.build_hasher()); + } + + leaf == root + } + + /// Verify multiple `leaves` can be simultaneously proven to be a part of + /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` + /// and a custom hashing algorithm defined by `builder`. See + /// [`BuildHasher`] for more information on how to construct a builder. + /// + /// WARNING: This is a lower-level function. For most use cases, + /// [`Verifier::verify_multi_proof`], which uses `keccak256` as a hashing + /// algorithm, should be enough. Using other hashing algorithm may have + /// unexpected results. + /// + /// The `proof` must contain the sibling hashes one would need to rebuild + /// the root starting from `leaves`. `proof_flags` represents whether a + /// hash must be computed using a `proof` member. A new root is rebuilt by + /// starting from the `leaves` and traversing up the Merkle tree. + /// + /// The procedure incrementally reconstructs all inner nodes by combining + /// a leaf/inner node with either another leaf/inner node or a `proof` + /// sibling node, depending on each proof flag being true or false + /// respectively, i.e., the `i`-th hash must be computed using the proof if + /// `proof_flags[i] == false`. + /// + /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, + /// it is sufficient to ensure that: + /// - The tree is complete (but not necessarily perfect). + /// - The leaves to be proven are in the opposite order they appear in the + /// tree (i.e., as seen from right to left starting at the deepest layer + /// and continuing at the next layer). + /// + /// NOTE: This implementation is *not* equivalent to it's Solidity + /// counterpart. In Rust, access to uninitialized memory panics, which + /// means we don't need to check that the whole proof array has been + /// processed. Both implementations will revert for the same inputs, but + /// for different reasons. See + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `proof_flags` - A slice of booleans that determine whether to hash + /// leaves or the proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaves` - A slice of hashes that constitute the leaves of the merkle + /// tree to be proven, each leaf in bytes. + /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. + /// + /// # Errors + /// + /// Will return `Err` if the arguments are well-formed, but invalid. + /// + /// # Examples + /// + /// ```rust + /// use openzeppelin_crypto::{merkle::Verifier, KeccakBuilder}; + /// use hex_literal::hex; + /// + /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); + /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), + /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), + /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; + /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), + /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; + /// let proof_flags = [false, true, false, true]; + /// + /// let verification = + /// Verifier::verify_multi_proof_with_builder(&proof, &proof_flags, root, &leaves, &KeccakBuilder); + /// assert!(verification.unwrap()); + /// ``` + pub fn verify_multi_proof_with_builder( + proof: &[Bytes32], + proof_flags: &[bool], + root: Bytes32, + leaves: &[Bytes32], + builder: &B, + ) -> Result { + let total_hashes = proof_flags.len(); + if leaves.len() + proof.len() != total_hashes + 1 { + return Err(MultiProofError::InvalidTotalHashes); + } + if total_hashes == 0 { + // We can safely assume that either `leaves` or `proof` is not empty + // given the previous check. We use `unwrap_or_else` to avoid + // eagerly evaluating `proof[0]`, which may panic. + let rebuilt_root = *leaves.first().unwrap_or_else(|| &proof[0]); + return Ok(root == rebuilt_root); + } + + // `hashes` represents a queue of hashes, our "main queue". + let mut hashes = Vec::with_capacity(total_hashes + leaves.len()); + // Which initially gets populated with the leaves. + hashes.extend(leaves); + // The `xxx_pos` values are "pointers" to the next value to consume in + // each queue. We use them to mimic a queue's pop operation. + let mut proof_pos = 0; + let mut hashes_pos = 0; + // At each step, we compute the next hash using two values: + // - A value from the "main queue". Consume all the leaves, then all the + // hashes but the root. + // - A value from the "main queue" (merging branches) or a member of the + // `proof`, depending on `flag`. + for &flag in proof_flags { + let a = hashes[hashes_pos]; + hashes_pos += 1; + + let b; + if flag { + b = hashes + .get(hashes_pos) + .ok_or(MultiProofError::InvalidRootChild)?; + hashes_pos += 1; + } else { + b = proof + .get(proof_pos) + .ok_or(MultiProofError::InvalidProofLength)?; + proof_pos += 1; + }; + + let hash = commutative_hash_pair(a, *b, builder.build_hasher()); + hashes.push(hash); + } + + // We know that `total_hashes > 0`. + let rebuilt_root = hashes[total_hashes + leaves.len() - 1]; + Ok(root == rebuilt_root) + } +} + +/// An error that occurred while verifying a multi-proof. +/// +/// TODO: Once is resolved, +/// we should derive `core::error::Error`. +#[derive(core::fmt::Debug)] +pub enum MultiProofError { + /// The proof length does not match the flags. + InvalidProofLength, + /// Tried to access uninitialized memory. + /// + /// This happens when the proof is too long, which makes the verification + /// procedure try to access uninitialized memory, which may result in an + /// invalid root. + /// + /// For more information see [this vulnerability]. + /// + /// [this vulnerability]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wprv-93r4-jj2p + InvalidRootChild, + /// The number of leaves and proof members does not match the number of + /// hashes necessary to complete the verification. + InvalidTotalHashes, +} + +impl core::fmt::Display for MultiProofError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let msg = match self { + MultiProofError::InvalidProofLength => "invalid multi-proof length", + MultiProofError::InvalidRootChild => "invalid root child generated", + MultiProofError::InvalidTotalHashes => { + "leaves.len() + proof.len() != total_hashes + 1" + } + }; + + write!(f, "{msg}") + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + //! NOTE: The values used as input for these tests were all generated using + //! https://github.com/OpenZeppelin/merkle-tree. + use hex_literal::hex; + use rand::{thread_rng, RngCore}; + + use super::{Bytes32, KeccakBuilder, Verifier}; + use crate::hash::{commutative_hash_pair, BuildHasher}; + + /// Shorthand for declaring variables converted from a hex literal to a + /// fixed 32-byte slice. + macro_rules! bytes { + ($($var:ident = $hex:literal);* $(;)?) => { + $( + #[allow(non_upper_case_globals)] + const $var: Bytes32 = hex!($hex); + )* + }; + } + + /// Shorthand for converting from an array of hex literals to an array of + /// fixed 32-bytes slices. + macro_rules! bytes_array { + ($($s:literal),* $(,)?) => { + [ + $(hex!($s),)* + ] + }; + } + + #[test] + fn verifies_valid_proofs() { + // ```js + // const merkleTree = StandardMerkleTree.of( + // toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='), + // ['string'], + // ); + // + // const root = merkleTree.root; + // const hash = merkleTree.leafHash(['A']); + // const proof = merkleTree.getProof(['A']); + // ``` + bytes! { + root = "b89eb120147840e813a77109b44063488a346b4ca15686185cf314320560d3f3"; + leaf_a = "6efbf77e320741a027b50f02224545461f97cd83762d5fbfeb894b9eb3287c16"; + leaf_b = "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc"; + }; + let proof = bytes_array! { + "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc", + "1629d3b5b09b30449d258e35bbd09dd5e8a3abb91425ef810dc27eef995f7490", + "633d21baee4bbe5ed5c51ac0c68f7946b8f28d2937f0ca7ef5e1ea9dbda52e7a", + "8a65d3006581737a3bab46d9e4775dbc1821b1ea813d350a13fcd4f15a8942ec", + "d6c3f3e36cd23ba32443f6a687ecea44ebfe2b8759a62cccf7759ec1fb563c76", + "276141cd72b9b81c67f7182ff8a550b76eb96de9248a3ec027ac048c79649115", + }; + + let verification = Verifier::verify(&proof, root, leaf_a); + assert!(verification); + + let builder = KeccakBuilder.build_hasher(); + let no_such_leaf = commutative_hash_pair(leaf_a, leaf_b, builder); + let proof = &proof[1..]; + let verification = Verifier::verify(proof, root, no_such_leaf); + assert!(verification); + } + + #[test] + fn rejects_invalid_proofs() { + // ```js + // const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); + // const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']); + // + // const root = correctMerkleTree.root; + // const leaf = correctMerkleTree.leafHash(['a']); + // const proof = otherMerkleTree.getProof(['d']); + // ``` + bytes! { + root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; + leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + proof = "7b0c6cd04b82bfc0e250030a5d2690c52585e0cc6a4f3bc7909d7723b0236ece"; + }; + + let verification = Verifier::verify(&[proof], root, leaf); + assert!(!verification); + } + + #[test] + fn rejects_proofs_with_invalid_length() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); + // + // const root = merkleTree.root; + // const leaf = merkleTree.leafHash(['a']); + // const proof = merkleTree.getProof(['a']); + // ``` + bytes! { + root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; + leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + }; + let proof = bytes_array! { + "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", + "9cf5a63718145ba968a01c1d557020181c5b252f665cf7386d370eddb176517b", + }; + + let bad_proof = &proof[..1]; + let verification = Verifier::verify(bad_proof, root, leaf); + assert!(!verification); + } + + #[test] + fn verifies_valid_multi_proof() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); + // + // const root = merkleTree.root; + // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf')); + // const hashes = leaves.map(e => merkleTree.leafHash(e)); + // ``` + bytes! { + root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; + }; + let leaves = bytes_array! { + "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", + "c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848", + "eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b", + }; + let proof = bytes_array! { + "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e", + "8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f", + }; + + let proof_flags = [false, true, false, true]; + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(verification.unwrap()); + } + + #[test] + fn rejects_invalid_multi_proof() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); + // const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']); + // + // const root = merkleTree.root; + // const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi')); + // const hashes = leaves.map(e => merkleTree.leafHash(e)); + // ``` + bytes! { + root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; + }; + let leaves = bytes_array! { + "34e6ce3d0d73f6bff2ee1e865833d58e283570976d70b05f45c989ef651ef742", + "aa28358fb75b314c899e16d7975e029d18b4457fd8fd831f2e6c17ffd17a1d7e", + "e0fd7e6916ff95d933525adae392a17e247819ebecc2e63202dfec7005c60560", + }; + let proof = []; + let proof_flags = [true, true]; + + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(!verification.unwrap()); + } + + #[test] + fn errors_invalid_multi_proof_leaves() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); + // + // const root = merkleTree.root; + // const hashA = merkleTree.leafHash(['a']); + // const hashB = merkleTree.leafHash(['b']); + // const hashCD = hashPair( + // ethers.toBeArray(merkleTree.leafHash(['c'])), + // ethers.toBeArray(merkleTree.leafHash(['d'])), + // ); + // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) + // const fill = ethers.randomBytes(32); + // ``` + bytes! { + root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; + hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; + hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; + hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; + }; + + let mut random_bytes = [0u8; 32]; + thread_rng().fill_bytes(&mut random_bytes); + + let fill = Bytes32::from(random_bytes); + let proof = [hash_b, fill, hash_cd]; + let proof_flags = [false, false, false]; + let leaves = [hash_a, hash_e]; + + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(verification.is_err()); + } + + #[test] + fn errors_multi_proof_len_invalid() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); + // + // const root = merkleTree.root; + // const hashA = merkleTree.leafHash(['a']); + // const hashB = merkleTree.leafHash(['b']); + // const hashCD = hashPair( + // ethers.toBeArray(merkleTree.leafHash(['c'])), + // ethers.toBeArray(merkleTree.leafHash(['d'])), + // ); + // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) + // const fill = ethers.randomBytes(32); + // ``` + bytes! { + root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; + hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; + hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; + hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; + }; + + let mut random_bytes = [0u8; 32]; + thread_rng().fill_bytes(&mut random_bytes); + + let fill = Bytes32::from(random_bytes); + let proof = [hash_b, fill, hash_cd]; + let proof_flags = [false, false, false, false]; + let leaves = [hash_e, hash_a]; + + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(verification.is_err()); + } + + #[test] + fn verifies_single_leaf_multi_proof() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']); + // + // const root = merkleTree.root; + // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a')); + // const hashes = leaves.map(e => merkleTree.leafHash(e)); + // ``` + bytes!(root = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"); + let proof = []; + let proof_flags = []; + let leaves = [root]; + + let verification = Verifier::::verify_multi_proof( + &proof, + &proof_flags, + root, + &leaves, + ); + assert!(verification.unwrap()); + } + + #[test] + fn verifies_empty_leaves_multi_proof() { + // ```js + // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); + // + // const root = merkleTree.root; + // ``` + bytes!(root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"); + let proof = [root]; + let proof_flags = []; + let leaves = []; + + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(verification.unwrap()); + } + + #[test] + /// Errors when processing manipulated proofs with a zero-value node at + /// depth 1. + fn errors_manipulated_multi_proof() { + // ```js + // // Create a merkle tree that contains a zero leaf at depth 1 + // const leave = ethers.id('real leaf'); + // const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0)); + // + // // Now we can pass any **malicious** fake leaves as valid! + // const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id) + // .map(ethers.toBeArray).sort(Buffer.compare); + // const maliciousProof = [leave, leave]; + // const maliciousProofFlags = [true, true, false]; + // ``` + bytes! { + root = "f2d552e1e4c59d4f0fa2b80859febc9e4bdc915dff37c56c858550d8b64659a5"; + leaf = "5e941ddd8f313c0b39f92562c0eca709c3d91360965d396aaef584b3fa76889a"; + }; + let malicious_leaves = bytes_array! { + "1f23ad5fc0ee6ccbe2f3d30df856758f05ad9d03408a51a99c1c9f0854309db2", + "613994f4e324d0667c07857cd5d147994bc917da5d07ee63fc3f0a1fe8a18e34", + }; + let malicious_proof = [leaf, leaf]; + let malicious_proof_flags = [true, true, false]; + + let verification = Verifier::verify_multi_proof( + &malicious_proof, + &malicious_proof_flags, + root, + &malicious_leaves, + ); + assert!(verification.is_err()); + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml new file mode 100644 index 0000000000..c89b39f34e --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "e2e-proc" +description = "End-to-end Testing Procedural Macros" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true + +[lib] +proc-macro = true + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md new file mode 100644 index 0000000000..c9ebfaecfd --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md @@ -0,0 +1,5 @@ +# End-to-end Procedural Macros + +This crate contains procedural macros used in [e2e]. + +[e2e]: ../e2e/README.md diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs new file mode 100644 index 0000000000..f0f7bae2f2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs @@ -0,0 +1,21 @@ +#![doc = include_str!("../README.md")] +use proc_macro::TokenStream; + +mod test; + +/// Defines an end-to-end Stylus contract test that sets up `e2e::Account`s +/// based on the function's parameters. +/// +/// # Examples +/// +/// ```rust,ignore +/// #[e2e::test] +/// async fn foo(alice: Account, bob: Account) -> eyre::Result<()> { +/// let charlie = Account::new().await?; +/// // ... +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream { + test::test(&attr, input) +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs new file mode 100644 index 0000000000..f981d8950b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs @@ -0,0 +1,47 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, FnArg}; + +/// Shorthand to print nice errors. +macro_rules! error { + ($tokens:expr, $($msg:expr),+ $(,)?) => {{ + let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); + return error.to_compile_error().into(); + }}; + (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ + return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) + }}; +} + +/// Defines an end-to-end test that injects test accounts through parameters. +/// +/// For more information see [`crate::test`]. +pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { + let item_fn = parse_macro_input!(input as syn::ItemFn); + let attrs = &item_fn.attrs; + let sig = &item_fn.sig; + let fn_name = &sig.ident; + let fn_return_type = &sig.output; + let fn_stmts = &item_fn.block.stmts; + let fn_args = &sig.inputs; + + let account_declarations = fn_args.into_iter().map(|arg| { + let FnArg::Typed(arg) = arg else { + error!(arg, "unexpected receiver argument in test signature"); + }; + let account_arg_binding = &arg.pat; + let account_ty = &arg.ty; + quote! { + let #account_arg_binding = #account_ty::new().await?; + } + }); + quote! { + #( #attrs )* + #[tokio::test] + async fn #fn_name() #fn_return_type { + #( #account_declarations )* + #( #fn_stmts )* + } + } + .into() +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml new file mode 100644 index 0000000000..6d6cf933c7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "e2e" +description = "End-to-end Testing for Stylus" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true + +[dependencies] +alloy.workspace = true +tokio = { workspace = true, features = ["process"] } +eyre.workspace = true +regex.workspace = true +once_cell.workspace = true +koba.workspace = true +e2e-proc = { path = "../e2e-proc" } +toml = "0.8.13" + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/README.md b/target_chains/ethereum/sdk/stylus/lib/e2e/README.md new file mode 100644 index 0000000000..f2c4ad1e83 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/README.md @@ -0,0 +1,153 @@ +# End-to-end Testing for Stylus Contracts + +This end-to-end testing crate provides affordances to test your contracts in a +blockchain environment. + +This crate is currently coupled to [`nitro-testnode`] and [`koba`]. + +[`nitro-testnode`]: https://github.com/OffchainLabs/nitro-testnode + +[`koba`]: https://github.com/OpenZeppelin/koba + +## Usage + +Refer to our end-to-end tests [GitHub workflow] for a working example of a full +tests suite using the `e2e` crate. + +[GitHub workflow]: ../../.github/workflows/e2e-tests.yml + +### Accounts + +Decorate your tests with the `test` procedural macro: a thin wrapper over +`tokio::test` that sets up `Account`s for your test. + +```rust,ignore +#[e2e::test] +async fn accounts_are_funded(alice: Account) -> eyre::Result<()> { + let balance = alice.wallet.get_balance(alice.address()).await?; + let expected = parse_ether("10")?; + assert_eq!(expected, balance); + Ok(()) +} +``` + +A `Account` is a thin wrapper over a [`PrivateKeySigner`] and an `alloy` provider with a +[`WalletFiller`]. Both of them are connected to the RPC endpoint defined by the +`RPC_URL` environment variable. This means that a `Account` is the main proxy +between the RPC and the test code. + +All accounts start with 10 ETH as balance. You can have multiple accounts as +parameters of your test function, or you can create new accounts separately: + +```rust,ignore +#[e2e::test] +async fn foo(alice: Account, bob: Account) -> eyre::Result<()> { + let charlie = Account::new().await?; + // ... +} +``` + +[`LocalWallet`]: https://github.com/alloy-rs/alloy/blob/8aa54828c025a99bbe7e2d4fc9768605d172cc6d/crates/signer-local/src/lib.rs#L37 + +[`WalletFiller`]: https://github.com/alloy-rs/alloy/blob/8aa54828c025a99bbe7e2d4fc9768605d172cc6d/crates/provider/src/fillers/wallet.rs#L30 + +### Contracts + +We use `koba` to deploy contracts to the blockchain. This is not required, a +separate mechanism for deployment can be used. `Deployer` type exposes `Deployer::deploy` +method that abstracts away the mechanism used in our workflow. + +Given a Solidity contract with a constructor at path `src/constructor.sol` like +this: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Example { + mapping(address account => uint256) private _balances; + mapping(address account => mapping(address spender => uint256)) + private _allowances; + uint256 private _totalSupply; + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } +} +``` + +Account type exposes `Account::as_deployer` method that returns `Deployer` type. +It will facilitate deployment of the contract marked with the `#[entrypoint]` macro. +Then you can configure deployment with default constructor: + +```rust,ignore +let contract_addr = alice.as_deployer().deploy().await?.address()?; +``` + +Or with a custom constructor. +Note that the abi-encodable `Example::constructorCall` should be generated +with `sol!("src/constructor.sol")` macro. + +```rust,ignore +let ctr = Example::constructorCall { + name_: "Token".to_owned(), + symbol_: "TKN".to_owned(), +}; +let receipt = alice + .as_deployer() + .with_constructor(ctr) + .deploy() + .await?; +``` + +Then altogether, your first test case can look like this: + +```rust,ignore +sol!("src/constructor.sol") + +#[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + let ctr = Example::constructorCall { + name_: "Token".to_owned(), + symbol_: "TKN".to_owned(), + }; + let contract_addr = alice + .as_deployer() + .with_constructor(ctr) + .deploy() + .await? + .address()?; + let contract = Erc20::new(contract_addr, &alice.wallet); + + let Erc20::nameReturn { name } = contract.name().call().await?; + let Erc20::symbolReturn { symbol } = contract.symbol().call().await?; + + assert_eq!(name, TOKEN_NAME.to_owned()); + assert_eq!(symbol, TOKEN_SYMBOL.to_owned()); + Ok(()) +} +``` + +## Notice + +We maintain this crate on a best-effort basis. We use it extensively on our own +tests, so we will continue to add more affordances as we need them. + +That being said, please do open an issue to start a discussion, keeping in mind +our [code of conduct] and [contribution guidelines]. + +[code of conduct]: ../../CODE_OF_CONDUCT.md + +[contribution guidelines]: ../../CONTRIBUTING.md + +## Security + +> [!WARNING] +> This project is still in a very early and experimental phase. It has never +> been audited nor thoroughly reviewed for security vulnerabilities. Do not use +> in production. + +Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs new file mode 100644 index 0000000000..5e0e9559db --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs @@ -0,0 +1,119 @@ +use alloy::{ + network::EthereumWallet, + primitives::{Address, B256}, + providers::{Provider, ProviderBuilder}, + signers::{local::PrivateKeySigner, Signature, Signer}, +}; +use eyre::Result; +use once_cell::sync::Lazy; +use tokio::sync::{Mutex, MutexGuard}; + +use crate::{ + deploy::Deployer, + system::{fund_account, Wallet, RPC_URL_ENV_VAR_NAME}, +}; + +/// Type that corresponds to a test account. +#[derive(Clone, Debug)] +pub struct Account { + /// The account's local private key wrapper. + pub signer: PrivateKeySigner, + /// The account's wallet -- an `alloy` provider with a `WalletFiller`. + pub wallet: Wallet, +} + +impl Account { + /// Create a new account. + /// + /// # Errors + /// + /// May fail if funding the newly created account fails. + pub async fn new() -> Result { + AccountFactory::create().await + } + + /// Get a hex-encoded String representing this account's private key. + #[must_use] + pub fn pk(&self) -> String { + alloy::hex::encode(self.signer.to_bytes()) + } + + /// Retrieve this account's address. + #[must_use] + pub fn address(&self) -> Address { + self.signer.address() + } + + /// The rpc endpoint this account's provider is connect to. + #[must_use] + pub fn url(&self) -> &str { + self.wallet.client().transport().url() + } + + /// Sign the given hash. + /// + /// # Panics + /// + /// May fail when the method is not implemented for `Signer`. Should not + /// happen. + pub async fn sign_hash(&self, hash: &B256) -> Signature { + self.signer.sign_hash(hash).await.expect("should sign a hash") + } + + /// Sign the given message. + /// + /// # Panics + /// + /// May fail when the method is not implemented for `Signer`. Should not + /// happen. + pub async fn sign_message(&self, message: &[u8]) -> Signature { + self.signer.sign_message(message).await.expect("should sign a message") + } + + /// Create a configurable smart contract deployer on behalf of this account. + pub fn as_deployer(&self) -> Deployer { + Deployer::new(self.url().to_string(), self.pk()) + } +} + +/// A unit struct used as a synchronization mechanism in +/// [`SYNC_ACCOUNT_FACTORY`]. +struct AccountFactory; + +impl AccountFactory { + /// Get access to the factory in a synchronized manner. + async fn lock() -> MutexGuard<'static, Self> { + /// Since after wallet generation accounts get funded in the nitro test + /// node from a single "god" wallet, we must synchronize account + /// creation (otherwise the nonce will be too low). + static SYNC_ACCOUNT_FACTORY: Lazy> = + Lazy::new(|| Mutex::new(AccountFactory)); + + SYNC_ACCOUNT_FACTORY.lock().await + } + + /// Create new account and fund it via nitro test node access. + /// + /// # Errors + /// + /// May fail if unable to find the path to the node or if funding the newly + /// created account fails. + async fn create() -> eyre::Result { + let _lock = AccountFactory::lock().await; + + let signer = PrivateKeySigner::random(); + let addr = signer.address(); + fund_account(addr, "100")?; + + let rpc_url = std::env::var(RPC_URL_ENV_VAR_NAME) + .expect("failed to load RPC_URL var from env") + .parse() + .expect("failed to parse RPC_URL string into a URL"); + let wallet = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::from(signer.clone())) + .on_http(rpc_url); + + Ok(Account { signer, wallet }) + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs new file mode 100644 index 0000000000..a307472ea9 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs @@ -0,0 +1,68 @@ +use alloy::{rpc::types::TransactionReceipt, sol_types::SolConstructor}; +use koba::config::Deploy; + +use crate::project::Crate; + +/// A basic smart contract deployer. +pub struct Deployer { + rpc_url: String, + private_key: String, + ctr_args: Option, +} + +impl Deployer { + pub fn new(rpc_url: String, private_key: String) -> Self { + Self { rpc_url, private_key, ctr_args: None } + } + + /// Add solidity constructor to the deployer. + pub fn with_constructor( + mut self, + constructor: C, + ) -> Deployer { + self.ctr_args = Some(alloy::hex::encode(constructor.abi_encode())); + self + } + + /// Add the default constructor to the deployer. + pub fn with_default_constructor( + self, + ) -> Deployer { + self.with_constructor(C::default()) + } + + /// Deploy and activate the contract implemented as `#[entrypoint]` in the + /// current crate. + /// Consumes currently configured deployer. + /// + /// # Errors + /// + /// May error if: + /// + /// - Unable to collect information about the crate required for deployment. + /// - [`koba::deploy`] errors. + pub async fn deploy(self) -> eyre::Result { + let pkg = Crate::new()?; + let wasm_path = pkg.wasm; + let sol_path = pkg.manifest_dir.join("src/constructor.sol"); + + let config = Deploy { + generate_config: koba::config::Generate { + wasm: wasm_path.clone(), + sol: Some(sol_path), + args: self.ctr_args, + legacy: false, + }, + auth: koba::config::PrivateKey { + private_key_path: None, + private_key: Some(self.private_key), + keystore_path: None, + keystore_password_path: None, + }, + endpoint: self.rpc_url, + deploy_only: false, + quiet: false, + }; + koba::deploy(&config).await + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs new file mode 100644 index 0000000000..f0658dd421 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs @@ -0,0 +1,37 @@ +use std::{path::PathBuf, process::Command}; + +use eyre::Context; + +/// Gets expected path to the nitro test node. +pub(crate) fn get_node_path() -> eyre::Result { + let manifest_dir = get_workspace_root()?; + Ok(manifest_dir.join("nitro-testnode")) +} + +/// Runs the following command to get the worskpace root: +/// +/// ```bash +/// dirname "$(cargo locate-project --workspace --message-format plain)" +/// ``` +pub(crate) fn get_workspace_root() -> eyre::Result { + let output = Command::new("cargo") + .arg("locate-project") + .arg("--workspace") + .arg("--message-format") + .arg("plain") + .output() + .wrap_err("should run `cargo locate-project`")?; + + let manifest_path = String::from_utf8_lossy(&output.stdout); + let manifest_dir = Command::new("dirname") + .arg(&*manifest_path) + .output() + .wrap_err("should run `dirname`")?; + + let path = String::from_utf8_lossy(&manifest_dir.stdout) + .trim() + .to_string() + .parse::() + .wrap_err("failed to parse manifest dir path")?; + Ok(path) +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs new file mode 100644 index 0000000000..360ebd9cb2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs @@ -0,0 +1,116 @@ +use alloy::{ + sol_types::SolError, + transports::{RpcError, TransportErrorKind}, +}; + +/// Possible panic codes for a revert. +/// +/// Taken from +#[derive(Debug)] +#[allow(missing_docs)] // Pretty straightforward variant names. +pub enum PanicCode { + AssertionError = 0x1, + ArithmeticOverflow = 0x11, + DivisionByZero = 0x12, + EnumConversionOutOfBounds = 0x21, + IncorrectlyEncodedStorageByteArray = 0x22, + PopOnEmptyArray = 0x31, + ArrayAccessOutOfBounds = 0x32, + TooMuchMemoryAllocated = 0x41, + ZeroInitializedVariable = 0x51, +} + +impl core::fmt::Display for PanicCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let msg = match self { + PanicCode::AssertionError => + "Assertion error", + PanicCode::ArithmeticOverflow => + "Arithmetic operation overflowed outside of an unchecked block", + PanicCode::DivisionByZero => + "Division or modulo division by zero", + PanicCode::EnumConversionOutOfBounds => + "Tried to convert a value into an enum, but the value was too big or negative", + PanicCode::IncorrectlyEncodedStorageByteArray => + "Incorrectly encoded storage byte array", + PanicCode::PopOnEmptyArray => + ".pop() was called on an empty array", + PanicCode::ArrayAccessOutOfBounds => + "Array accessed at an out-of-bounds or negative index", + PanicCode::TooMuchMemoryAllocated => + "Too much memory was allocated, or an array was created that is too large", + PanicCode::ZeroInitializedVariable => + "Called a zero-initialized variable of internal function type" + }; + + write!(f, "{}", msg) + } +} + +/// An error representing a panic. +pub trait Panic { + /// Checks that `Self` corresponds to a panic with code `code`. + fn panicked_with(&self, code: PanicCode) -> bool; +} + +/// An error representing a revert with some data. +pub trait Revert { + /// Checks that `Self` corresponds to the typed abi-encoded error + /// `expected`. + fn reverted_with(&self, expected: E) -> bool; +} + +impl Panic for alloy::contract::Error { + fn panicked_with(&self, _code: PanicCode) -> bool { + let Self::TransportError(e) = self else { + return false; + }; + + // FIXME: right now we cannot have any better error code for Panics + // check `e`: + // ErrorResp( + // ErrorPayload { + // code: -32000, + // message: "execution reverted", + // data: None, + // }, + // ) + let payload = e.as_error_resp().expect("should contain payload"); + payload.code == -32000 && payload.message == "execution reverted" + } +} + +impl Revert for alloy::contract::Error { + fn reverted_with(&self, expected: E) -> bool { + let Self::TransportError(e) = self else { + return false; + }; + + let raw_value = e + .as_error_resp() + .and_then(|payload| payload.data.clone()) + .expect("should extract the error"); + let actual = &raw_value.get().trim_matches('"')[2..]; + let expected = alloy::hex::encode(expected.abi_encode()); + expected == actual + } +} + +impl Revert for eyre::Report { + fn reverted_with(&self, expected: E) -> bool { + let Some(received) = self + .chain() + .find_map(|err| err.downcast_ref::>()) + else { + return false; + }; + let RpcError::ErrorResp(received) = received else { + return false; + }; + let Some(received) = &received.data else { + return false; + }; + let expected = alloy::hex::encode(expected.abi_encode()); + received.to_string().contains(&expected) + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs new file mode 100644 index 0000000000..5d6cd76a39 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs @@ -0,0 +1,23 @@ +use alloy::{rpc::types::eth::TransactionReceipt, sol_types::SolEvent}; + +/// Extension trait for asserting an event gets emitted. +pub trait EventExt { + /// Asserts the contract emitted the `expected` event. + fn emits(&self, expected: E) -> bool; +} + +impl EventExt for TransactionReceipt +where + E: SolEvent, + E: PartialEq, +{ + fn emits(&self, expected: E) -> bool { + // Extract all events that are the expected type. + self.inner + .logs() + .iter() + .filter_map(|log| log.log_decode().ok()) + .map(|log| log.inner.data) + .any(|event| expected == event) + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs new file mode 100644 index 0000000000..539f71aa7d --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs @@ -0,0 +1,92 @@ +#![doc = include_str!("../README.md")] +mod account; +mod deploy; +mod environment; +mod error; +mod event; +mod project; +mod receipt; +mod system; + +pub use account::Account; +pub use e2e_proc::test; +pub use error::{Panic, PanicCode, Revert}; +pub use event::EventExt; +pub use receipt::ReceiptExt; +pub use system::{fund_account, provider, Provider, Wallet}; + +/// This macro provides a shorthand for broadcasting the transaction to the +/// network. +/// +/// See: +/// +/// # Examples +/// +/// ```rust,ignore +/// #[e2e::test] +/// async fn foo(alice: Account) -> eyre::Result<()> { +/// let contract_addr = alice.as_deployer().deploy().await?.address()?; +/// let contract = Erc721::new(contract_addr, &alice.wallet); +/// +/// let alice_addr = alice.address(); +/// let token_id = random_token_id(); +/// let pending_tx = send!(contract.mint(alice_addr, token_id))?; +/// // ... +/// } +#[macro_export] +macro_rules! send { + ($e:expr) => { + $e.send().await + }; +} + +/// This macro provides a shorthand for broadcasting the transaction +/// to the network, and then waiting for the given number of confirmations. +/// +/// See: +/// +/// # Examples +/// +/// ```rust,ignore +/// #[e2e::test] +/// async fn foo(alice: Account) -> eyre::Result<()> { +/// let contract_addr = alice.as_deployer().deploy().await?.address()?; +/// let contract = Erc721::new(contract_addr, &alice.wallet); +/// +/// let alice_addr = alice.address(); +/// let token_id = random_token_id(); +/// let result = watch!(contract.mint(alice_addr, token_id))?; +/// // ... +/// } +#[macro_export] +macro_rules! watch { + ($e:expr) => { + $crate::send!($e)?.watch().await + }; +} + +/// This macro provides a shorthand for broadcasting the transaction +/// to the network, waiting for the given number of confirmations, and then +/// fetching the transaction receipt. +/// +/// See: +/// +/// # Examples +/// +/// ```rust,ignore +/// #[e2e::test] +/// async fn foo(alice: Account) -> eyre::Result<()> { +/// let contract_addr = alice.as_deployer().deploy().await?.address()?; +/// let contract = Erc721::new(contract_addr, &alice.wallet); +/// +/// let alice_addr = alice.address(); +/// let token_id = random_token_id(); +/// let receipt = receipt!(contract.mint(alice_addr, token_id))?; +/// // ... +/// } +#[macro_export] +macro_rules! receipt { + ($e:expr) => { + $crate::send!($e)?.get_receipt().await + }; +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs new file mode 100644 index 0000000000..537d8fa597 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs @@ -0,0 +1,105 @@ +use std::{ + env, + ffi::OsStr, + fs::File, + io::{BufReader, Read}, + path::{Path, PathBuf}, +}; + +use eyre::bail; +use toml::Table; + +/// Information about the crate subject of an integration test. +pub(crate) struct Crate { + /// Path to the directory where the crate's manifest lives. + pub(crate) manifest_dir: PathBuf, + /// Path to the compiled wasm binary. + pub(crate) wasm: PathBuf, +} + +impl Crate { + /// Collects crate information from the environment. + /// + /// # Errors + /// + /// May error if: + /// + /// - The current working directory is invalid. + /// - Could not read the package name from the manifest file. + /// - Could not read the path to the compiled wasm binary. + pub(crate) fn new() -> eyre::Result { + let manifest_dir = env::current_dir()?; + let name = read_pkg_name(&manifest_dir)?; + let wasm = get_wasm(&name)?; + + Ok(Self { manifest_dir, wasm }) + } +} + +/// Reads and parses the package name from a manifest in `path`. +/// +/// # Errors +/// +/// May error if: +/// +/// - Unable to parse the `Cargo.toml` at `path`. +/// - Unable to read the package name from the parsed toml file. +fn read_pkg_name>(path: P) -> eyre::Result { + let cargo_toml = path.as_ref().join("Cargo.toml"); + + let mut reader = BufReader::new(File::open(cargo_toml)?); + let mut buffer = String::new(); + reader.read_to_string(&mut buffer)?; + + let table = buffer.parse::()?; + let name = table["package"]["name"].as_str(); + + match name { + Some(x) => Ok(x.to_owned()), + None => Err(eyre::eyre!("unable to find package name in toml")), + } +} + +/// Returns the path to the compiled wasm binary with name `name`. +/// +/// Note that this function works for both workspaces and standalone crates. +/// +/// # Errors +/// +/// May error if: +/// +/// - Unable to read the current executable's path. +/// - The output directory is not `target`. +fn get_wasm(name: &str) -> eyre::Result { + let name = name.replace('-', "_"); + // Looks like + // "rust-contracts-stylus/target/debug/deps/erc721-15764c2c9a33bee7". + let mut target_dir = env::current_exe()?; + + // Recursively find a `target` directory. + loop { + let Some(parent) = target_dir.parent() else { + // We've found `/`. + bail!("output directory is not 'target'"); + }; + + target_dir = parent.to_path_buf(); + let Some(leaf) = target_dir.file_name() else { + // We've found the root because we are traversing a canonicalized + // path, which means there are no `..` segments, and we started at + // the executable. + bail!("output directory is not 'target'"); + }; + + if leaf == OsStr::new("target") { + break; + } + } + + let wasm = target_dir + .join("wasm32-unknown-unknown") + .join("release") + .join(format!("{name}.wasm")); + + Ok(wasm) +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs new file mode 100644 index 0000000000..631a250ef1 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs @@ -0,0 +1,17 @@ +use alloy::{ + network::ReceiptResponse, primitives::Address, + rpc::types::TransactionReceipt, +}; +use eyre::ContextCompat; + +/// Extension trait to recover address of the contract that was deployed. +pub trait ReceiptExt { + /// Returns the address of the contract from the [`TransactionReceipt`]. + fn address(&self) -> eyre::Result
; +} + +impl ReceiptExt for TransactionReceipt { + fn address(&self) -> eyre::Result
{ + self.contract_address().context("should contain contract address") + } +} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs new file mode 100644 index 0000000000..aef9ac3ecb --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs @@ -0,0 +1,85 @@ +use alloy::{ + network::{Ethereum, EthereumWallet}, + primitives::Address, + providers::{ + fillers::{ + ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, + WalletFiller, + }, + Identity, ProviderBuilder, RootProvider, + }, + transports::http::{Client, Http}, +}; +use eyre::{bail, Context}; + +use crate::environment::get_node_path; + +pub(crate) const RPC_URL_ENV_VAR_NAME: &str = "RPC_URL"; + +/// Convenience type alias that represents an Ethereum wallet. +pub type Wallet = FillProvider< + JoinFill< + JoinFill< + JoinFill, NonceFiller>, + ChainIdFiller, + >, + WalletFiller, + >, + RootProvider>, + Http, + Ethereum, +>; + +/// Convenience type alias that represents an alloy provider. +pub type Provider = FillProvider< + JoinFill< + JoinFill, NonceFiller>, + ChainIdFiller, + >, + RootProvider>, + Http, + Ethereum, +>; + +/// Load the `name` environment variable. +fn env(name: &str) -> eyre::Result { + std::env::var(name).wrap_err(format!("failed to load {name}")) +} + +/// Returns an alloy provider connected to the `RPC_URL` rpc endpoint. +/// +/// # Panics +/// +/// May panic if unable to load the `RPC_URL` environment variable. +#[must_use] +pub fn provider() -> Provider { + let rpc_url = env(RPC_URL_ENV_VAR_NAME) + .expect("failed to load RPC_URL var from env") + .parse() + .expect("failed to parse RPC_URL string into a URL"); + ProviderBuilder::new().with_recommended_fillers().on_http(rpc_url) +} + +/// Send `amount` eth to `address` in the nitro-tesnode. +pub fn fund_account(address: Address, amount: &str) -> eyre::Result<()> { + let node_script = get_node_path()?.join("test-node.bash"); + if !node_script.exists() { + bail!("Test nitro node wasn't setup properly. Try to setup it first with `./scripts/nitro-testnode.sh -i -d`") + }; + + let output = std::process::Command::new(node_script) + .arg("script") + .arg("send-l2") + .arg("--to") + .arg(format!("address_{address}")) + .arg("--ethamount") + .arg(amount) + .output()?; + + if !output.status.success() { + let err = String::from_utf8_lossy(&output.stderr); + bail!("account's wallet wasn't funded - address is {address}:\n{err}") + } + + Ok(()) +} diff --git a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml index ed43909973..ac4fdf8bb5 100644 --- a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml @@ -15,7 +15,6 @@ stylus-sdk.workspace = true mini-alloc.workspace = true stylus-proc.workspace = true - [lib] crate-type = ["lib", "cdylib"] @@ -23,6 +22,3 @@ crate-type = ["lib", "cdylib"] export-abi = ["stylus-sdk/export-abi"] debug = ["stylus-sdk/debug"] -[[bin]] -name = "mock" -path = "src/main.rs" diff --git a/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs b/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs deleted file mode 100644 index 7b6d4ca4b3..0000000000 --- a/target_chains/ethereum/sdk/stylus/mock/example/pythtest.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Example on how to interact with a deployed `stylus-hello-world` contract using defaults. -//! This example uses ethers-rs to instantiate the contract using a Solidity ABI. -//! Then, it attempts to check the current counter value, increment it via a tx, -//! and check the value again. The deployed contract is fully written in Rust and compiled to WASM -//! but with Stylus, it is accessible just as a normal Solidity smart contract is via an ABI. - -use ethers::{ - middleware::SignerMiddleware, - prelude::abigen, - providers::{Http, Middleware, Provider}, - signers::{LocalWallet, Signer}, - types::Address, -}; -use dotenv::dotenv; -use eyre::eyre; -use std::io::{BufRead, BufReader}; -use std::str::FromStr; -use std::sync::Arc; - -/// Your private key file path. -const PRIV_KEY_PATH: &str = "PRIV_KEY_PATH"; - -/// Stylus RPC endpoint url. -const RPC_URL: &str = "RPC_URL"; - -/// Deployed pragram address. -const STYLUS_CONTRACT_ADDRESS: &str = "STYLUS_CONTRACT_ADDRESS"; - -#[tokio::main] -async fn main() -> eyre::Result<()> { - dotenv().ok(); - let priv_key_path = - std::env::var(PRIV_KEY_PATH).map_err(|_| eyre!("No {} env var set", PRIV_KEY_PATH))?; - let rpc_url = std::env::var(RPC_URL).map_err(|_| eyre!("No {} env var set", RPC_URL))?; - let contract_address = std::env::var(STYLUS_CONTRACT_ADDRESS) - .map_err(|_| eyre!("No {} env var set", STYLUS_CONTRACT_ADDRESS))?; - - abigen!( - PythUse, - r#"[ - function initialize(address ipth_address) external; - function number(bytes32 id) external; - ]"# - ); - - let provider = Provider::::try_from(rpc_url)?; - let address: Address = contract_address.parse()?; - - let privkey = read_secret_from_file(&priv_key_path)?; - let wallet = LocalWallet::from_str(&privkey)?; - let chain_id = provider.get_chainid().await?.as_u64(); - let client = Arc::new(SignerMiddleware::new( - provider, - wallet.clone().with_chain_id(chain_id), - )); - - // let counter = Counter::new(address, client); - // let num = counter.number().call().await; - // println!("Counter number value = {:?}", num); - - // let pending = counter.increment(); - // if let Some(receipt) = pending.send().await?.await? { - // println!("Receipt = {:?}", receipt); - // } - // println!("Successfully incremented counter via a tx"); - - // let num = counter.number().call().await; - // println!("New counter number value = {:?}", num); - // Ok(()) -} - -fn read_secret_from_file(fpath: &str) -> eyre::Result { - let f = std::fs::File::open(fpath)?; - let mut buf_reader = BufReader::new(f); - let mut secret = String::new(); - buf_reader.read_line(&mut secret)?; - Ok(secret.trim().to_string()) -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs index cffaba510a..190b554b41 100644 --- a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs @@ -1,12 +1,10 @@ #![cfg_attr(not(feature = "export-abi"), no_main)] extern crate alloc; -use core::borrow; -use std::fmt::Result; /// Import items from the SDK. The prelude contains common traits and macros. use stylus_sdk::{ prelude::*, stylus_proc::entrypoint, alloy_sol_types::sol}; -use pyth_stylus::pyth::{PythContract, get_ema_price_unsafe}; +use pyth_stylus::pyth::get_ema_price_unsafe; use alloy_primitives::{ FixedBytes, Address }; // Define some persistent storage using the Solidity ABI. diff --git a/target_chains/ethereum/sdk/stylus/mock/src/main.rs b/target_chains/ethereum/sdk/stylus/mock/src/main.rs deleted file mode 100644 index 7c6d15313f..0000000000 --- a/target_chains/ethereum/sdk/stylus/mock/src/main.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![cfg_attr(not(feature = "export-abi"), no_main)] - -#[cfg(feature = "export-abi")] -fn main() { - mock::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;"); -} \ No newline at end of file From e3302d8aabc1da339528d3d66293f0425b68f8a6 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 14 Oct 2024 15:02:03 +0000 Subject: [PATCH 025/160] chore: removed projects --- .../ethereum/sdk/stylus/.cargo/config.toml | 14 ++----- .../ethereum/sdk/stylus/example/Cargo.toml | 13 ------ .../ethereum/sdk/stylus/example/src/main.rs | 3 -- .../ethereum/sdk/stylus/mock/Cargo.toml | 24 ----------- .../ethereum/sdk/stylus/mock/privateKey.txt | 0 .../ethereum/sdk/stylus/mock/src/lib.rs | 42 ------------------- .../ethereum/sdk/stylus/tests/Cargo.toml | 13 ------ .../ethereum/sdk/stylus/tests/src/main.rs | 3 -- 8 files changed, 3 insertions(+), 109 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/example/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/example/src/main.rs delete mode 100644 target_chains/ethereum/sdk/stylus/mock/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/mock/privateKey.txt delete mode 100644 target_chains/ethereum/sdk/stylus/mock/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/tests/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/tests/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/.cargo/config.toml b/target_chains/ethereum/sdk/stylus/.cargo/config.toml index 8c46df4b48..77cf9f2648 100644 --- a/target_chains/ethereum/sdk/stylus/.cargo/config.toml +++ b/target_chains/ethereum/sdk/stylus/.cargo/config.toml @@ -1,16 +1,8 @@ [target.wasm32-unknown-unknown] -rustflags = [ - "-C", "link-arg=-zstack-size=32768", -] +rustflags = ["-C", "link-arg=-zstack-size=8192"] [target.aarch64-apple-darwin] -rustflags = [ -"-C", "link-arg=-undefined", -"-C", "link-arg=dynamic_lookup", -] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] [target.x86_64-apple-darwin] -rustflags = [ -"-C", "link-arg=-undefined", -"-C", "link-arg=dynamic_lookup", -] +rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] diff --git a/target_chains/ethereum/sdk/stylus/example/Cargo.toml b/target_chains/ethereum/sdk/stylus/example/Cargo.toml deleted file mode 100644 index 3134bfdcbe..0000000000 --- a/target_chains/ethereum/sdk/stylus/example/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "example" -authors.workspace = true -license.workspace = true -keywords.workspace = true -repository.workspace = true -version.workspace = true -edition.workspace = true - -[dependencies] - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/example/src/main.rs b/target_chains/ethereum/sdk/stylus/example/src/main.rs deleted file mode 100644 index e7a11a969c..0000000000 --- a/target_chains/ethereum/sdk/stylus/example/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml b/target_chains/ethereum/sdk/stylus/mock/Cargo.toml deleted file mode 100644 index ac4fdf8bb5..0000000000 --- a/target_chains/ethereum/sdk/stylus/mock/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "mock" -authors.workspace = true -license.workspace = true -keywords.workspace = true -repository.workspace = true -version.workspace = true -edition.workspace = true - -[dependencies] -pyth-stylus.workspace = true -alloy-primitives.workspace = true -alloy-sol-types.workspace = true -stylus-sdk.workspace = true -mini-alloc.workspace = true -stylus-proc.workspace = true - -[lib] -crate-type = ["lib", "cdylib"] - -[features] -export-abi = ["stylus-sdk/export-abi"] -debug = ["stylus-sdk/debug"] - diff --git a/target_chains/ethereum/sdk/stylus/mock/privateKey.txt b/target_chains/ethereum/sdk/stylus/mock/privateKey.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs b/target_chains/ethereum/sdk/stylus/mock/src/lib.rs deleted file mode 100644 index 190b554b41..0000000000 --- a/target_chains/ethereum/sdk/stylus/mock/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![cfg_attr(not(feature = "export-abi"), no_main)] -extern crate alloc; - - -/// Import items from the SDK. The prelude contains common traits and macros. -use stylus_sdk::{ prelude::*, stylus_proc::entrypoint, alloy_sol_types::sol}; -use pyth_stylus::pyth::get_ema_price_unsafe; -use alloy_primitives::{ FixedBytes, Address }; - -// Define some persistent storage using the Solidity ABI. -// `Counter` will be the entrypoint. - -sol!{ - error AlreadyInitialized(); -} - - -#[derive(SolidityError)] -pub enum PythUseError { - - AlreadyInitialized(AlreadyInitialized), -} - -sol_storage! { - #[entrypoint] - pub struct PythUse { - address pyth; - } -} - -/// Declare that `Counter` is a contract with the following external methods. -#[public] -impl PythUse { - pub fn initialize(&mut self, ipth_address: Address) { - self.pyth.set(ipth_address); - } - /// Gets the number from storage. - pub fn number(&mut self, id:FixedBytes<32>) { - let _ = get_ema_price_unsafe(self, self.pyth.get(), id); - - } -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/tests/Cargo.toml b/target_chains/ethereum/sdk/stylus/tests/Cargo.toml deleted file mode 100644 index 62edb503cf..0000000000 --- a/target_chains/ethereum/sdk/stylus/tests/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "tests" -authors.workspace = true -license.workspace = true -keywords.workspace = true -repository.workspace = true -version.workspace = true -edition.workspace = true - -[dependencies] - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/tests/src/main.rs b/target_chains/ethereum/sdk/stylus/tests/src/main.rs deleted file mode 100644 index e7a11a969c..0000000000 --- a/target_chains/ethereum/sdk/stylus/tests/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} From 824d76769bbb1354575522bf116870e83e6483e4 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 14 Oct 2024 15:02:58 +0000 Subject: [PATCH 026/160] feture :completed unit test and stylus contracts --- target_chains/ethereum/sdk/stylus/Cargo.lock | 21 - target_chains/ethereum/sdk/stylus/Cargo.toml | 2 +- .../ethereum/sdk/stylus/contracts/Cargo.toml | 1 - .../ethereum/sdk/stylus/contracts/src/lib.rs | 49 +- .../sdk/stylus/contracts/src/pyth/errors.rs | 11 +- .../sdk/stylus/contracts/src/pyth/events.rs | 15 +- .../stylus/contracts/src/pyth/functions.rs | 70 +++ .../sdk/stylus/contracts/src/pyth/ipyth.rs | 29 +- .../sdk/stylus/contracts/src/pyth/mock.rs | 441 +++++++++++------- .../sdk/stylus/contracts/src/pyth/mod.rs | 255 ++++------ .../contracts/src/pyth/pyth_aggregator_V3.rs | 0 .../sdk/stylus/contracts/src/pyth/solidity.rs | 104 +++-- .../sdk/stylus/contracts/src/utils/helpers.rs | 23 +- 13 files changed, 547 insertions(+), 474 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index d4968f1c5b..215819e87e 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -529,10 +529,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "example" -version = "0.0.1" - [[package]] name = "fastrand" version = "2.1.1" @@ -788,18 +784,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "mock" -version = "0.0.1" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "mini-alloc 0.4.2", - "pyth-stylus", - "stylus-proc", - "stylus-sdk", -] - [[package]] name = "motsu" version = "0.1.0-rc" @@ -1356,7 +1340,6 @@ dependencies = [ "keccak-const", "lazy_static", "mini-alloc 0.6.0", - "regex", "stylus-proc", ] @@ -1419,10 +1402,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "tests" -version = "0.0.1" - [[package]] name = "thiserror" version = "1.0.64" diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index b09a9173ea..5f935507f1 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "contracts", "example", "tests","mock" + "contracts" ] default-members = [ "contracts", diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index 580dd06c20..415e72bafa 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -14,7 +14,6 @@ stylus-sdk.workspace = true mini-alloc.workspace = true motsu = "=0.1.0-rc" - [features] # Enables using the standard library. This is not included in the default # features, because this crate is meant to be used in a `no_std` environment. diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 9c312c6bd7..01a47b1255 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -1,56 +1,11 @@ -/*! -# OpenZeppelin Contracts for Stylus - -A library for secure smart contract development written in Rust for -[Arbitrum Stylus](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). -This library offers common smart contract primitives and affordances that take -advantage of the nature of Stylus. - -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - -## Usage - -To start using it, add `openzeppelin-stylus` to your `Cargo.toml`, or simply run -`cargo add openzeppelin-stylus`. - -```toml -[dependencies] -openzeppelin-stylus = "x.x.x" -``` - -We recommend pinning to a specific version -- expect rapid iteration. - -Once defined as a dependency, use one of our pre-defined implementations by -importing them: - -```ignore -use openzeppelin_stylus::token::erc20::Erc20; - -sol_storage! { - #[entrypoint] - struct MyContract { - #[borrow] - Erc20 erc20; - } -} - -#[external] -#[inherit(Erc20)] -impl MyContract { } -``` -*/ - - #![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; -#[global_allocator] -static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +// #[global_allocator] +// static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod utils; pub mod pyth; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs index 4a308f0eab..e81a2eb45c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -88,11 +88,17 @@ sol! { #[derive(Debug)] #[allow(missing_docs)] error InvalidWormholeAddressToSet(); + + #[derive(Debug)] + #[allow(missing_docs)] + error FalledDecodeData(); + + } /// A Pausable error. #[derive(SolidityError, Debug)] -pub enum IPythError { +pub enum Error { InvalidArgument(InvalidArgument), InvalidUpdateDataSource(InvalidUpdateDataSource), InvalidUpdateData(InvalidUpdateData), @@ -107,9 +113,10 @@ pub enum IPythError { InvalidGovernanceDataSource(InvalidGovernanceDataSource), OldGovernanceMessage(OldGovernanceMessage), InvalidWormholeAddressToSet(InvalidWormholeAddressToSet), + FalledDecodeData(FalledDecodeData), } -impl MethodError for IPythError { +impl MethodError for Error { fn encode(self) -> alloc::vec::Vec { self.into() } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs index 7c13ea3319..04a4b12e7f 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs @@ -1,8 +1,11 @@ -//! Various Solidity definitions, including ABI-compatible interfaces, events, functions, etc. +use alloy_sol_types::sol; -use stylus_sdk::alloy_sol_types::sol; -// sol! { - -// event PriceFeedUpdate(); -// } +sol! { + event PriceFeedUpdate( + bytes32 indexed id, + uint64 publishTime, + int64 price, + uint64 conf + ); +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs new file mode 100644 index 0000000000..effa218b4b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -0,0 +1,70 @@ +use crate::pyth::solidity::{ + getEmaPriceUnsafeCall, + getEmaPriceNoOlderThanCall, + getPriceUnsafeCall, + getValidTimePeriodCall, + getUpdateFeeCall, + getPriceNoOlderThanCall, + parsePriceFeedUpdatesCall, + parsePriceFeedUpdatesUniqueCall, + updatePriceFeedsIfNecessaryCall, + updatePriceFeedsCall, + Price, + PriceFeed +}; +use crate::utils::helpers::{call_helper, delegate_call_helper}; +use alloc::vec::Vec; +use stylus_sdk::storage::TopLevelStorage; +use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; + + +pub(crate) fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { + let price_call = call_helper::(storage, pyth_address, (id,age,))?; + Ok(price_call.price) +} + +pub(crate) fn get_update_fee(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result> { + let update_fee_call = call_helper::(storage, pyth_address, (update_data,))?; + Ok(update_fee_call.feeAmount) +} + +pub(crate) fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { + let ema_price = call_helper::(storage, pyth_address, (id,))?; + Ok(ema_price.price) +} + + +pub(crate) fn get_ema_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { + let ema_price = call_helper::(storage, pyth_address, (id,age,))?; + Ok(ema_price.price) +} + +pub(crate) fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { + let price = call_helper::(storage, pyth_address, (id,))?; + Ok(price.price) +} + +pub(crate) fn get_valid_time_period(storage: &mut impl TopLevelStorage,pyth_address: Address) -> Result> { + let valid_time_period = call_helper::(storage, pyth_address, ())?; + Ok(valid_time_period.validTimePeriod) +} + +pub(crate) fn update_price_feeds(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result<(), Vec> { + delegate_call_helper::(storage, pyth_address, (update_data,))?; + Ok(()) +} + +pub(crate) fn update_price_feeds_if_necessary(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Vec> { + delegate_call_helper::(storage, pyth_address, (update_data,price_ids, publish_times))?; + Ok(()) +} + +pub(crate) fn parse_price_feed_updates(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { + let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; + Ok(parse_price_feed_updates_call.priceFeeds) +} + +pub(crate) fn parse_price_feed_updates_unique(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { + let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; + Ok(parse_price_feed_updates_call.priceFeeds) +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs index ddee42fa75..504c461080 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs @@ -1,30 +1,47 @@ use crate::pyth::solidity::{Price,PriceFeed}; use alloc::vec::Vec; +use alloy_primitives::U256; use stylus_sdk::{abi::Bytes, alloy_primitives::FixedBytes}; pub trait IPyth { + type Error: Into>; - fn get_price_unsafe(self, id: FixedBytes<32>) -> Result; - fn get_price_no_older_than(id: FixedBytes<32>, age: u8) -> Result; - fn get_ema_price_unsafe(id: FixedBytes<32>) -> Result; + + fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + + fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error>; + + fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error>; + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + fn update_price_feeds_if_necessary( &mut self, update_data: Vec, price_ids: Vec>, publish_times: Vec, ) -> Result<(), Self::Error>; - fn get_update_fee(&self, update_data: Vec) -> Result; + + fn get_update_fee(&mut self, update_data: Vec) -> Result; + fn parse_price_feed_updates( + &mut self, update_data: Vec, price_ids: Vec>, min_publish_time: u64, max_publish_time: u64, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; + + fn parse_price_feed_updates_unique( + &mut self, update_data: Vec, price_ids: Vec>, min_publish_time: u64, max_publish_time: u64, - ) -> Result; + ) -> Result, Self::Error>; + + } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 26130dc0e5..c264a5ff1a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -1,47 +1,44 @@ +use core::borrow::BorrowMut; + use alloc::vec::Vec; -use alloy_primitives::{ Uint, U64}; -use stylus_sdk::{prelude::*,msg, abi::Bytes, alloy_primitives::{Address, FixedBytes, U256}}; -use crate::pyth::{ - errors::{IPythError, PriceFeedNotFound, InsufficientFee}, - solidity::{StoragePriceFeed}, - //events::PriceFeedUpdate -}; -use crate::pyth::PythContract; +use alloy_primitives::{Uint, U64}; +use alloy_sol_types::{abi, sol_data::Uint as SolUInt, SolType, SolValue}; +use stylus_sdk::{abi::Bytes, alloy_primitives::{FixedBytes, U256}, evm, msg, prelude::*}; +use crate::{pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE}; +use crate::pyth::errors::{Error, PriceFeedNotFound}; +use crate::pyth::solidity::{ PriceFeed,Price, StoragePriceFeed}; +use crate::pyth::events::PriceFeedUpdate; +use crate::pyth::ipyth::IPyth; +//decode data type +pub type DecodeDataType = (PriceFeed, SolUInt<64>); sol_storage! { - pub struct MockPythContract { + struct MockPythContract { uint single_update_fee_in_wei; uint valid_time_period; mapping(bytes32 => StoragePriceFeed) price_feeds; - #[borrow] - PythContract pyth; } } - -//#[public] -//#[inherit(PythContract)] +#[public] impl MockPythContract { - pub fn initalize(mut self, single_update_fee_in_wei: U256, valid_time_period: U256) { - self.single_update_fee_in_wei.set(single_update_fee_in_wei); - self.valid_time_period.set(valid_time_period); - } - // pub fn guery_price_feed(self, id: FixedBytes<32>) -> Result> { - // let price_feed = self.price_feeds.getter(id).load(); - // if price_feed.id.is_empty() { - // return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound {}).into()); - // } - // Ok(price_feed.load()) - // } - pub fn price_feed_exists(self, id:FixedBytes<32>) -> bool { - self.price_feeds.getter(id).id.is_empty() == false - } - - pub fn get_valid_time_period(self) -> Uint<256, 4> { + fn query_price_feed(&self, id: FixedBytes<32>) -> Result, Vec> { + let price_feed = self.price_feeds.get(id).to_price_feed(); + if price_feed.id.eq(&FixedBytes::<32>::ZERO) { + return Err(Error::PriceFeedNotFound(PriceFeedNotFound {}).into()); + } + Ok(price_feed.abi_encode()) + } + + fn price_feed_exists(&self, id:FixedBytes<32>) -> bool { + self.price_feeds.getter(id).id.is_empty() + } + + fn get_valid_time_period(&self) -> Uint<256, 4> { self.valid_time_period.get() - } + } // Takes an array of encoded price feeds and stores them. // You can create this data either by calling createPriceFeedUpdateData or @@ -58,147 +55,241 @@ impl MockPythContract { // uint64 prevPublishTime // ) // ] - // pub fn update_price_feeds(self, - // update_data : Vec - // ) -> Result<(), Vec> { - // let required_fee = self.get_update_fee(update_data); - // if msg.value < required_fee { - // return Err(IPythError::InsufficientFee(InsufficientFee {}).into()); - // } - // if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - - // for (uint i = 0; i < updateData.length; i++) { - // PythStructs.PriceFeed memory priceFeed = abi.decode( - // updateData[i], - // (PythStructs.PriceFeed) - // ); - - // uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime; - - // if (lastPublishTime < priceFeed.price.publishTime) { - // // Price information is more recent than the existing price information. - // priceFeeds[priceFeed.id] = priceFeed; - // emit PriceFeedUpdate( - // priceFeed.id, - // uint64(priceFeed.price.publishTime), - // priceFeed.price.price, - // priceFeed.price.conf - // ); - // } - // } - // } - - pub fn get_update_fee(self, - update_data: Vec - ) -> Uint<256, 4> { - self.single_update_fee_in_wei.get() * U256::from(update_data.len()) - } - - // pub fn parse_price_feed_updates_internal( - // update_data: Vec>, - // priceIds: Vec>, - // min_publish_time:u64, - // max_publish_time:u64, - // unique:bool - // ) internal returns (PythStructs.PriceFeed[] memory feeds) { - // uint requiredFee = getUpdateFee(updateData); - // if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - - // feeds = new PythStructs.PriceFeed[](priceIds.length); - - // for (uint i = 0; i < priceIds.length; i++) { - // for (uint j = 0; j < updateData.length; j++) { - // uint64 prevPublishTime; - // (feeds[i], prevPublishTime) = abi.decode( - // updateData[j], - // (PythStructs.PriceFeed, uint64) - // ); - - // uint publishTime = feeds[i].price.publishTime; - // if (priceFeeds[feeds[i].id].price.publishTime < publishTime) { - // priceFeeds[feeds[i].id] = feeds[i]; - // emit PriceFeedUpdate( - // feeds[i].id, - // uint64(publishTime), - // feeds[i].price.price, - // feeds[i].price.conf - // ); - // } - - // if (feeds[i].id == priceIds[i]) { - // if ( - // minPublishTime <= publishTime && - // publishTime <= maxPublishTime && - // (!unique || prevPublishTime < minPublishTime) - // ) { - // break; - // } else { - // feeds[i].id = 0; - // } - // } - // } - - // if (feeds[i].id != priceIds[i]) - // revert PythErrors.PriceFeedNotFoundWithinRange(); - // } - // } - - // function parsePriceFeedUpdates( - // bytes[] calldata updateData, - // bytes32[] calldata priceIds, - // uint64 minPublishTime, - // uint64 maxPublishTime - // ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - // return - // parsePriceFeedUpdatesInternal( - // updateData, - // priceIds, - // minPublishTime, - // maxPublishTime, - // false - // ); - // } - - // function parsePriceFeedUpdatesUnique( - // bytes[] calldata updateData, - // bytes32[] calldata priceIds, - // uint64 minPublishTime, - // uint64 maxPublishTime - // ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - // return - // parsePriceFeedUpdatesInternal( - // updateData, - // priceIds, - // minPublishTime, - // maxPublishTime, - // true - // ); - // } - - // function createPriceFeedUpdateData( - // bytes32 id, - // int64 price, - // uint64 conf, - // int32 expo, - // int64 emaPrice, - // uint64 emaConf, - // uint64 publishTime, - // uint64 prevPublishTime - // ) public pure returns (bytes memory priceFeedData) { - // PythStructs.PriceFeed memory priceFeed; - - // priceFeed.id = id; - - // priceFeed.price.price = price; - // priceFeed.price.conf = conf; - // priceFeed.price.expo = expo; - // priceFeed.price.publishTime = publishTime; - - // priceFeed.emaPrice.price = emaPrice; - // priceFeed.emaPrice.conf = emaConf; - // priceFeed.emaPrice.expo = expo; - // priceFeed.emaPrice.publishTime = publishTime; - - // priceFeedData = abi.encode(priceFeed, prevPublishTime); - // } -} \ No newline at end of file + + #[payable] + fn update_price_feeds(&mut self, + update_data : Vec + ) -> Result<(), Vec> { + let required_fee = self.get_update_fee(update_data.clone()); + if required_fee.lt(&U256::from(msg::value())) { + return Err(Error::InsufficientFee(InsufficientFee {}).into()); + } + + for i in 0..update_data.len() { + let price_feed_data = ::abi_decode(&update_data[i], false) + .map_err( |_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec())?; + let last_publish_time = &self.price_feeds.get(price_feed_data.id).price.publish_time; + if last_publish_time.lt(&price_feed_data.price.publish_time) { + self.price_feeds.setter(price_feed_data.id).set(price_feed_data); + evm::log(PriceFeedUpdate { + id: price_feed_data.id, + publishTime: price_feed_data.price.publish_time.to(), + price: price_feed_data.price.price, + conf: price_feed_data.price.conf + }); + } + } + Ok(()) + } + + fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { + self.single_update_fee_in_wei.get() * U256::from(update_data.len()) + } + + fn parse_price_feed_updates( + &mut self, + update_data:Vec, + price_ids:Vec>, + min_publish_time:u64, + max_publish_time:u64 + ) -> Result, Vec>{ + self.parse_price_feed_updates_internal(update_data, price_ids, min_publish_time, max_publish_time, false) + } + + + fn parse_price_feed_updates_unique( + &mut self, + update_data:Vec, + price_ids:Vec>, + min_publish_time:u64, + max_publish_time:u64 + ) -> Result, Vec>{ + self.parse_price_feed_updates_internal(update_data, price_ids, min_publish_time, max_publish_time, true) + } + + fn create_price_feed_update_data(&self, + id:FixedBytes<32>, + price:i64, + conf:u64, + expo:i32, + ema_price:i64, + ema_conf:u64, + publish_time:U256, + prev_publish_time:u64 + ) -> Vec { + let price = Price { price: price, conf, expo, publish_time }; + let ema_price = Price { price:ema_price, conf:ema_conf, expo:expo,publish_time}; + + let price_feed_data = PriceFeed { + id,price,ema_price + }; + + let price_feed_data_encoding = (price_feed_data, prev_publish_time); + return DecodeDataType::abi_encode(&price_feed_data_encoding); + } +} + +impl MockPythContract { + + fn parse_price_feed_updates_internal(&mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time:u64, + max_publish_time:u64, + unique:bool + ) -> Result, Vec> { + let required_fee = self.get_update_fee(update_data.clone()); + if required_fee.lt(&U256::from(msg::value())) { + return Err(Error::InsufficientFee(InsufficientFee {}).into()); + } + + let mut feeds = Vec::::with_capacity(price_ids.len()); + + for i in 0..price_ids.len() { + for j in 0..update_data.len() { + let (price_feed, prev_publish_time) = match DecodeDataType::abi_decode(&update_data[j], false) { + Ok(res) =>{ res }, + Err(_) => { + return Err(Error::FalledDecodeData(FalledDecodeData {}).into()) + } + }; + feeds[j] = price_feed.clone(); + + let publish_time = price_feed.price.publish_time; + if self.price_feeds.get(feeds[i].id).price.publish_time.lt(&publish_time) { + self.price_feeds.setter(feeds[i].id).set(feeds[i]); + evm::log( + PriceFeedUpdate { + id:feeds[i].id, + publishTime: publish_time.to(), + price: feeds[i].price.price, + conf: feeds[i].price.conf + }); + } + + + if feeds[i].id == price_ids[i] { + if publish_time.gt(&U256::from(min_publish_time)) + && publish_time.le(&U256::from(max_publish_time)) && + (!unique || prev_publish_time.lt(&min_publish_time)) + { + break; + } else { + feeds[i].id = FixedBytes::<32>::ZERO; + } + } + + } + + if feeds[i].id != price_ids[i] { + return Err(Error::FalledDecodeData(FalledDecodeData {}).into()) + } + } + Ok(feeds.abi_encode()) + } +} + + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloc::vec; + use alloy_primitives::{address, uint, Address, U64, U256, FixedBytes, fixed_bytes}; + use stylus_sdk::{abi::Bytes, contract, msg::{self, value}}; + use crate::pyth::{ + PythContract, mock::{MockPythContract, DecodeDataType}, + errors::{Error, InvalidArgument}, solidity::{PriceFeed, Price, StoragePriceFeed} + }; + use alloy_sol_types::SolType; + + use std::{println as info, println as warn}; + + // Updated constants to use uppercase naming convention + const PRICE: i64 = 1000; + const CONF: u64 = 1000; + const EXPO: i32 = 1000; + const EMA_PRICE: i64 = 1000; + const EMA_CONF: u64 = 1000; + const PREV_PUBLISH_TIME: u64 = 1000; + + fn generate_bytes() -> FixedBytes<32> { + FixedBytes::<32>::repeat_byte(30) + } + + #[motsu::test] + fn can_initialize_mock_contract(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + assert_eq!(contract.single_update_fee_in_wei.get(), U256::from(1000)); + assert_eq!(contract.valid_time_period.get(), U256::from(1000)); + } + + #[motsu::test] + fn error_initialize_mock_contract(contract: MockPythContract) { + let err = contract.initialize(U256::from(0), U256::from(0)).expect_err("should not initialize with invalid parameters"); + } + + #[motsu::test] + fn created_price_feed_data(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let id = generate_bytes(); + let publish_time = U256::from(1000); + let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, publish_time, PREV_PUBLISH_TIME); + let price_feed_decoded = DecodeDataType::abi_decode(&price_feed_created, true).unwrap(); + assert_eq!(price_feed_decoded.0.id, id); + assert_eq!(price_feed_decoded.0.price.price, PRICE); + assert_eq!(price_feed_decoded.0.price.conf, CONF); + assert_eq!(price_feed_decoded.0.price.expo, EXPO); + assert_eq!(price_feed_decoded.0.ema_price.price, EMA_PRICE); + assert_eq!(price_feed_decoded.0.ema_price.conf, EMA_CONF); + assert_eq!(price_feed_decoded.1, PREV_PUBLISH_TIME); + } + + #[motsu::test] + fn can_get_update_fee(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let publish_time = U256::from(1000); + let mut update_data: Vec = vec![]; + let mut x = 0; + while x < 10 { + let id = generate_bytes(); + let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, publish_time, PREV_PUBLISH_TIME); + update_data.push(Bytes::from(price_feed_created)); + x += 1; + } + let required_fee = contract.get_update_fee(update_data.clone()); + assert_eq!(required_fee, U256::from(1000 * x)); + } + + #[motsu::test] + fn price_feed_does_not_exist(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let id = generate_bytes(); + let price_feed_found = contract.price_feed_exists(id); + assert_eq!(price_feed_found, false); + } + + #[motsu::test] + fn query_price_feed_failed(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let id = generate_bytes(); + let _price_feed = contract.query_price_feed(id).expect_err("should not query if price feed does not exist"); + } + + #[motsu::test] + fn can_get_valid_time_period(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let valid_time_period = contract.get_valid_time_period(); + assert_eq!(valid_time_period, U256::from(1000)); + } + + #[motsu::test] + fn can_update_price_feeds(contract: MockPythContract) { + let _ = contract.initialize(U256::from(1000), U256::from(1000)); + let id = generate_bytes(); + let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, U256::from(1000), PREV_PUBLISH_TIME); + let mut update_data: Vec = vec![]; + update_data.push(Bytes::from(price_feed_created)); + //let balance = contrac; + info!("{:?} ", msg::sender()); + //contract.update_price_feeds(update_data) + } +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 3e974da2e3..77d5775132 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,190 +1,113 @@ -use crate::pyth::errors::{IPythError, PriceFeedNotFound, StalePrice}; -use crate::pyth::solidity::{ - getEmaPriceUnsafeCall, - // getPriceUnsafeCall, - // updatePriceFeedsCall, - StoragePriceFeed, - StoragePrice -}; -use crate::utils::helpers::{call_helper,delegate_call_helper, CALL_RETDATA_DECODING_ERROR_MESSAGE}; +use alloy_sol_types::SolValue; +use ipyth::IPyth; +use solidity::Price; +use stylus_sdk::{abi::Bytes as AbiBytes, block, prelude::*, storage::TopLevelStorage}; +use errors::{Error, FalledDecodeData, StalePrice}; use alloc::vec::Vec; -use stylus_sdk::{console,prelude::*,storage::{TopLevelStorage, StorageAddress, StorageMap},alloy_sol_types::sol}; -use alloy_primitives::{ FixedBytes,U256, Bytes, Address }; +use alloy_primitives::{ Address, FixedBytes, U256 , Bytes }; +use functions::{ + get_price_unsafe, + get_price_no_older_than, + get_ema_price_unsafe, + get_ema_price_no_older_than, + update_price_feeds, + update_price_feeds_if_necessary, + get_update_fee, + parse_price_feed_updates, + parse_price_feed_updates_unique +}; -pub mod errors; -pub mod events; -//pub mod ipyth; -pub mod solidity; -pub mod mock; +mod errors; +mod events; +mod ipyth; +mod solidity; +mod mock; +mod functions; sol_storage! { - #[entrypoint] - pub struct PythContract { + struct PythContract { address _ipyth; } } -type Price = StoragePrice; -type PriceFeed = StoragePriceFeed; +unsafe impl TopLevelStorage for PythContract {} -pub fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result<(), Vec> { - let _get_call = call_helper::(storage, pyth_address, (id,))?; - console!("{:?}",_get_call); - // Ok(()); - // let price = Price::abi_decode(&get_call, false).map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()); - // Ok(price) +#[public] +impl IPyth for PythContract { - Ok(()) -} + type Error = Vec; -#[public] -impl PythContract { - pub fn initialize(&mut self, ipth_address: Address) { - self._ipyth.set(ipth_address); + fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + let price = get_price_unsafe(self, self._ipyth.get(), id)?; + let data = price.abi_encode(); + Ok(data) } - // pub fn price_feed_exists(self, id: FixedBytes<32>) -> bool { - // self.price_feeds.getter(id).id.is_empty() - // } - - - // pub fn query_price_feed(self, id: FixedBytes<32>) -> Result> { - // let price_found = self.price_feeds.getter(id).to_price_feed(); - // if price_found.id.is_empty() { - // return Err(IPythError::PriceFeedNotFound(PriceFeedNotFound {}).into()); - // } - // Ok(price_found) - // } - - // pub fn get_price_unsafe>( - // self, - // storage: &mut S, - // id: FixedBytes<32>, - // ) -> Result> { - // let price = call_helper::(storage, self._ipyth.get(), (id,)).unwrap(); - // Ok(price) - // } - - // pub fn get_price_no_older_than>( - // self, - // storage: &mut S, - // id: FixedBytes<32>, - // age: U256, - // ) -> Result> { - // let price = self.get_ema_price_unsafe(storage, id)?; - // if Self::_diff(U256::from(block::timestamp()), price.publish_time) > age { - // return Err(IPythError::StalePrice(StalePrice {}).into()); - // } - // Ok(price) - // } - - pub fn get_ema_price_unsafe_test( - &mut self, - id: FixedBytes<32>, - ) -> Result<(), Vec> { - let data = get_ema_price_unsafe(self, self._ipyth.get(), id); - console!("{:?}",data); - Ok(()) + + fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error> { + let price = get_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + let data = price.abi_encode(); + Ok(data) } - // pub fn get_ema_price_no_older_than>( - // self, - // storage: &mut S, - // id: FixedBytes<32>, - // age: U256, - // ) -> Result> { - // let price = - // call_helper::(storage, self._ipyth.get(), (id,)); - - // if Self::_diff(U256::from(block::timestamp()), price.publish_time) > age { - // return Err(IPythError::StalePrice(StalePrice {}).into()); - // } - // Ok(price) - // } - - - // fn _update_price_feeds_if_necessary>( - // self, - // storage: &mut S, - // update_data:Vec, - // price_ids: Vec>, - // publish_times: Vec - // ) -> Result<(), Vec> { - // if update_data.len() != publish_times.len() { - // return Err(IPythError::InvalidArgument(InvalidArgument{}).into()); - // } - // //let item = self; - // //for i in 0..price_ids.len() { - // // let mut i = 0; - // // loop { - // // self._check_valid_price_feed(price_ids[i]); - // // self._check_valid_query(price_ids[i], publish_times[i]); - // // } - // // { - - // //} - - // // } - // return Err(IPythError::NoFreshUpdate(NoFreshUpdate{}).into()); - // } - - - - // pub fn update_price_feeds>( - // self, - // storage: &mut S, - // update_data: Vec, - // ) -> Result<(), Vec> { - // //update_data = SolBytes::from(update_data.into()); - // delegate_call_helper::(storage, self._ipyth.get(), (update_data,))?; - // Ok(()) - // } - -} + fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + let price = get_ema_price_unsafe(self, self._ipyth.get(), id)?; + let data = price.abi_encode(); + Ok(data) + } -impl PythContract { - // fn _diff(x: U256, y: U256) -> U256 { - // if x > y { - // return x - y; - // } - // y - x - // } - - // fn _check_valid_query( - // self, - // price_id: FixedBytes<32>, - // publish_time: u64 - // ) -> bool { - // true - // // return Self::query_price_feed(self,price_id).unwrap().price.publish_time < U256::from(publish_time); - // } - - // fn _check_valid_price_feed( - // self, - // price_id: FixedBytes<32>, - // ) -> bool { - // return self.price_feeds.getter(price_id).id.is_empty() - // } -} + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error> { + let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + let data = price.abi_encode(); + Ok(data) + } + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + update_price_feeds(self, self._ipyth.get(),data) + } -#[cfg(all(test, feature = "std"))] -mod tests { - use alloy_primitives::{address, uint, Address, U256}; - use stylus_sdk::msg; - use crate::pyth::PythContract; + fn update_price_feeds_if_necessary( + &mut self, + update_data: Vec, + price_ids: Vec>, + publish_times: Vec, + ) -> Result<(), Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times) + } - #[motsu::test] - fn check_invalid_ipyth_address(contract: PythContract) { - let unsafe_call = contract.get_ema_price_unsafe(Address::ZERO); - // assert_eq!(U256::ZERO, balance); + fn get_update_fee(&mut self, update_data: Vec) -> Result { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let fee = get_update_fee(self, self._ipyth.get(), data)?; + Ok(fee) + } - // let owner = msg::sender(); - // let one = uint!(1_U256); - // contract._balances.setter(owner).set(one); - // let balance = contract.balance_of(owner); - // assert_eq!(one, balance); + fn parse_price_feed_updates( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + Ok(encode_data) } + + fn parse_price_feed_updates_unique( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + Ok(encode_data) + } } + + diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_aggregator_V3.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs index a203bbbb4f..b7eaf4fc0f 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs @@ -1,6 +1,9 @@ -use alloc::vec::Vec; -use stylus_sdk::{prelude::*, alloy_sol_types::{sol,}}; +use alloc::string::ToString; +use alloy_primitives::{hex::encode, Signed, I32, I64, U256, U32, U64}; +use alloy_sol_types::SolValue; +use stylus_sdk::{alloy_sol_types::sol, prelude::*, ArbResult,abi::internal::EncodableReturnType}; + sol_storage! { pub struct StoragePrice { @@ -13,7 +16,7 @@ sol_storage! { // Unix timestamp describing when the price was published uint publish_time; } - + pub struct StoragePriceFeed { bytes32 id; StoragePrice price; @@ -22,28 +25,39 @@ sol_storage! { } - sol! { + #[derive(Debug, Copy)] + struct Price { + int64 price; + uint64 conf; + int32 expo; + uint publish_time; + } - function getPriceUnsafe( - bytes32 id - ) external view; + #[derive(Debug, Copy)] + struct PriceFeed { + bytes32 id; + Price price; + Price ema_price; + } + + function getPriceUnsafe(bytes32 id) external view returns (Price memory price); - function getPriceNoOlderThan( + function getPriceNoOlderThan( bytes32 id, uint age - ) external view; + ) external view returns (Price memory price); function getEmaPriceUnsafe( bytes32 id - ) external view ; + ) external view returns (Price memory price) ; function getEmaPriceNoOlderThan( bytes32 id, uint age - ) external view ; + ) external view returns (Price memory price) ; function updatePriceFeeds(bytes[] calldata updateData) external payable; @@ -63,7 +77,7 @@ sol! { bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external payable ; + ) external payable returns (PriceFeed[] memory priceFeeds) ; function parsePriceFeedUpdatesUnique( @@ -71,16 +85,16 @@ sol! { bytes32[] calldata priceIds, uint64 minPublishTime, uint64 maxPublishTime - ) external payable ; + ) external payable returns (PriceFeed[] memory priceFeeds); function queryPriceFeed( bytes32 id - ) public view virtual ; + ) public view virtual returns (PriceFeed[] memory priceFeeds); function priceFeedExists( bytes32 id - ) public view virtual ; + ) public view virtual returns (PriceFeed[] memory priceFeeds); function getValidTimePeriod() @@ -91,24 +105,42 @@ sol! { } -// impl StoragePrice { -// pub fn to_price(&self) ->Price { -// Price { -// price:self.price.get().as_i64(), -// conf: self.conf.get().to(), -// expo: self.expo.get().as_i32(), -// publish_time: self.publish_time.get() -// } -// } -// } -// impl StoragePriceFeed { -// pub fn to_price_feed(&self)-> PriceFeed { -// PriceFeed { -// id: self.id.get(), -// price: self.price.to_price(), -// emaPrice: self.ema_price.to_price() -// } -// } -// } - - \ No newline at end of file + + +impl StoragePrice { + pub fn to_price(&self) ->Price { + Price { + price:self.price.get().as_i64(), + conf: self.conf.get().to(), + expo: self.expo.get().as_i32(), + publish_time: self.publish_time.get() + } + } + + pub fn set(&mut self, price:Price) { + self.price.set(I64::try_from(price.price).unwrap()); + self.conf.set(U64::try_from(price.conf).unwrap()); + self.expo.set(I32::try_from(price.expo).unwrap()); + self.publish_time.set(price.publish_time); + } + +} + +impl StoragePriceFeed { + pub fn to_price_feed(&self)-> PriceFeed { + PriceFeed { + id: self.id.get(), + price: self.price.to_price(), + ema_price: self.ema_price.to_price() + } + } + + pub fn set(&mut self, price_feed:PriceFeed) { + self.id.set(price_feed.id); + self.price.set(price_feed.price); + self.ema_price.set(price_feed.ema_price); + } + +} + +//impl for Price {} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs index caaffd015d..41a28ae648 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs @@ -3,6 +3,7 @@ use { alloy_sol_types::{ SolCall, SolType, + }, stylus_sdk::{ alloy_primitives::Address, @@ -38,14 +39,12 @@ pub fn delegate_call_helper( storage: &mut impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result, Vec> { +) -> Result> { let calldata = C::new(args).abi_encode(); let res = unsafe { delegate_call(storage, address, &calldata).map_err(map_call_error)? }; - Ok(res) - //C//::abi_decode(data, validate) - // C::abi_decode_returns(&res, false /* validate */) - // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } /// Performs a `staticcall` to the given address, calling the function defined as a `SolCall` with the given arguments @@ -54,12 +53,11 @@ pub fn static_call_helper( storage: &impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result, Vec> { +) -> Result> { let calldata = C::new(args).abi_encode(); let res = static_call(storage, address, &calldata).map_err(map_call_error)?; - Ok(res) - // C::abi_decode_returns(&res, false /* validate */) - // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } /// Performs a `call` to the given address, calling the function @@ -68,10 +66,9 @@ pub fn call_helper( storage: &mut impl TopLevelStorage, address: Address, args: as SolType>::RustType, -) -> Result, Vec> { +) -> Result> { let calldata = C::new(args).abi_encode(); let res = call(storage, address, &calldata).map_err(map_call_error)?; - Ok(res) - // C::abi_decode_returns(&res, false /* validate */) - // .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } From 60baf08069464e8975bb8d96db19d2420a1df5b7 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 14 Oct 2024 16:40:58 +0000 Subject: [PATCH 027/160] added initializtion for mock --- .../ethereum/sdk/stylus/contracts/src/pyth/mock.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index c264a5ff1a..3118fef9db 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -11,7 +11,7 @@ use crate::pyth::events::PriceFeedUpdate; use crate::pyth::ipyth::IPyth; //decode data type -pub type DecodeDataType = (PriceFeed, SolUInt<64>); +pub(crate) type DecodeDataType = (PriceFeed, SolUInt<64>); sol_storage! { struct MockPythContract { @@ -23,7 +23,17 @@ sol_storage! { #[public] impl MockPythContract { - + + fn initialize(&mut self, single_update_fee_in_wei: Uint<256, 4>, valid_time_period: Uint<256, 4>) -> Result<(), Vec> { + if single_update_fee_in_wei <= U256::from(0) || valid_time_period <= U256::from(0) { + return Err(Error::InvalidArgument(InvalidArgument {}).into()); + } + self.single_update_fee_in_wei.set(single_update_fee_in_wei); + self.valid_time_period.set(valid_time_period); + Ok(()) + } + + fn query_price_feed(&self, id: FixedBytes<32>) -> Result, Vec> { let price_feed = self.price_feeds.get(id).to_price_feed(); if price_feed.id.eq(&FixedBytes::<32>::ZERO) { From 86ad529757a7532ab3e273daff086e5b02e9d3a2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 14 Oct 2024 16:41:14 +0000 Subject: [PATCH 028/160] examples setup --- target_chains/ethereum/sdk/stylus/Cargo.lock | 2976 ++++++++++++++++- target_chains/ethereum/sdk/stylus/Cargo.toml | 20 +- .../external-call-contracts/Cargo.toml | 22 + .../external-call-contracts/src/main.rs | 3 + .../stylus/examples/function-calls/Cargo.toml | 10 + .../examples/function-calls/src/main.rs | 3 + 6 files changed, 2857 insertions(+), 177 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 215819e87e..71fbab1cc0 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2,6 +2,43 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli 0.31.1", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -11,6 +48,178 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "alloy" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba1c79677c9ce51c8d45e20845b05e6fb070ea2c863fba03ad6af2c778474bd" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", +] + +[[package]] +name = "alloy-chains" +version = "0.1.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156bfc5dcd52ef9a5f33381701fa03310317e14c65093a9430d3e3557b08dcd3" +dependencies = [ + "alloy-primitives 0.8.8", + "num_enum", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da374e868f54c7f4ad2ad56829827badca388efd645f8cf5fccc61c2b5343504" +dependencies = [ + "alloy-eips", + "alloy-primitives 0.7.6", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc6957ff706f9e5f6fd42f52a93e4bce476b726c92d077b348de28c4a76730c" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-primitives 0.7.6", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + +[[package]] +name = "alloy-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af3faff14c12c8b11037e0a093dd157c3702becb8435577a2408534d0758315" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives 0.7.6", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.7.6", + "alloy-sol-type-parser", + "alloy-sol-types", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eips" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76ecab54890cdea1e4808fc0891c7e6cfcf71fe1a9fe26810c7280ef768f4ed" +dependencies = [ + "alloy-primitives 0.7.6", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-genesis" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca15afde1b6d15e3fc1c97421262b1bbb37aee45752e3c8b6d6f13f776554ff" +dependencies = [ + "alloy-primitives 0.7.6", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" +dependencies = [ + "alloy-primitives 0.7.6", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6f34930b7e3e2744bcc79056c217f00cb2abb33bc5d4ff88da7623c5bb078b" +dependencies = [ + "alloy-primitives 0.7.6", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f6895fc31b48fa12306ef9b4f78b7764f8bd6d7d91cdb0a40e233704a0f23f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-primitives 0.7.6", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "thiserror", +] + [[package]] name = "alloy-primitives" version = "0.7.6" @@ -21,7 +230,8 @@ dependencies = [ "bytes", "cfg-if 1.0.0", "const-hex", - "derive_more", + "derive_more 0.99.18", + "getrandom", "hex-literal", "itoa", "k256", @@ -33,16 +243,169 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-primitives" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f35429a652765189c1c5092870d8360ee7b7769b09b06d89ebaefd34676446" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more 1.0.0", + "hex-literal", + "itoa", + "paste", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-provider" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c538bfa893d07e27cb4f3c1ab5f451592b7c526d511d62b576a2ce59e146e4a" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives 0.7.6", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tracing", + "url", +] + [[package]] name = "alloy-rlp" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ + "alloy-rlp-derive", "arrayvec", "bytes", ] +[[package]] +name = "alloy-rlp-derive" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba31bae67773fd5a60020bea900231f8396202b7feca4d0c70c6b59308ab4a8" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "alloy-transport-http", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184a7a42c7ba9141cc9e76368356168c282c3bc3d9e5d78f3556bdfe39343447" +dependencies = [ + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4123ee21f99ba4bd31bfa36ba89112a18a500f8b452f02b35708b1b951e2b9" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives 0.7.6", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.13.0", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9416c52959e66ead795a11f4a86c248410e9e368a0765710e57055b8a1774dd6" +dependencies = [ + "alloy-primitives 0.7.6", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33753c09fa1ad85e5b092b8dc2372f1e337a42e84b9b4cff9fede75ba4adb32" +dependencies = [ + "alloy-primitives 0.7.6", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-local" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfc9c26fe6c6f1bad818c9a976de9044dd12e1f75f1f156a801ee3e8148c1b6" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives 0.7.6", + "alloy-signer", + "async-trait", + "elliptic-curve", + "eth-keystore", + "k256", + "rand", + "thiserror", +] + [[package]] name = "alloy-sol-macro" version = "0.7.6" @@ -63,10 +426,11 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", - "indexmap", + "indexmap 2.6.0", "proc-macro-error", "proc-macro2", "quote", @@ -81,27 +445,122 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck", "proc-macro2", "quote", + "serde_json", "syn 2.0.79", "syn-solidity", ] +[[package]] +name = "alloy-sol-type-parser" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +dependencies = [ + "winnow", +] + [[package]] name = "alloy-sol-types" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" dependencies = [ - "alloy-primitives", + "alloy-json-abi", + "alloy-primitives 0.7.6", "alloy-sol-macro", "const-hex", "serde", ] +[[package]] +name = "alloy-transport" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b51a291f949f755e6165c3ed562883175c97423703703355f4faa4b7d0a57c" +dependencies = [ + "alloy-json-rpc", + "base64", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-http" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d65871f9f1cafe1ed25cde2f1303be83e6473e995a2d56c275ae4fcce6119c" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -132,7 +591,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -233,10 +692,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "auto_impl" -version = "1.2.0" +name = "async-stream" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", @@ -244,17 +714,60 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.4.0" +name = "async-trait" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "base16ct" -version = "0.2.0" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -276,6 +789,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -303,12 +822,72 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byte-slice-cast" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -320,6 +899,30 @@ name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +dependencies = [ + "serde", +] + +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] [[package]] name = "cc" @@ -342,6 +945,62 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "const-hex" version = "1.13.1" @@ -376,6 +1035,35 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "corosensei" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + [[package]] name = "cpufeatures" version = "0.2.14" @@ -385,6 +1073,114 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" +dependencies = [ + "arrayvec", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-egraph", + "cranelift-entity", + "cranelift-isle", + "gimli 0.26.2", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" + +[[package]] +name = "cranelift-egraph" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" +dependencies = [ + "cranelift-entity", + "fxhash", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "log", + "smallvec", +] + +[[package]] +name = "cranelift-entity" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" + +[[package]] +name = "cranelift-frontend" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crunchy" version = "0.2.2" @@ -413,6 +1209,62 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "der" version = "0.7.9" @@ -447,6 +1299,27 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -474,6 +1347,29 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "e2e" +version = "0.1.0" +dependencies = [ + "alloy", + "e2e-proc", + "eyre", + "koba", + "once_cell", + "regex", + "tokio", + "toml", +] + +[[package]] +name = "e2e-proc" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -513,6 +1409,47 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enumset" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -529,6 +1466,57 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid 0.8.2", +] + +[[package]] +name = "external-call-contracts" +version = "0.0.0" +dependencies = [ + "alloy", + "alloy-primitives 0.7.6", + "e2e", + "eyre", + "mini-alloc 0.4.2", + "stylus-sdk", + "tokio", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.1.1" @@ -574,12 +1562,150 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "function-calls" +version = "0.0.1" + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -602,6 +1728,29 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.13.0" @@ -613,11 +1762,31 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -625,11 +1794,20 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -646,6 +1824,116 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -666,6 +1954,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[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", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -673,9 +1977,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", ] +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -685,12 +2010,30 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.4" @@ -730,10 +2073,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" [[package]] -name = "lazy_static" -version = "1.5.0" +name = "koba" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "80e92e1148d087df999396266311bece9e5311c352821872cccaf5dc67117cfc" +dependencies = [ + "alloy", + "brotli2", + "bytesize", + "clap", + "eyre", + "hex", + "once_cell", + "owo-colors", + "regex", + "tempfile", + "tokio", + "wasmer", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" @@ -753,18 +2122,85 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[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 = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "memory_units" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mini-alloc" version = "0.4.2" @@ -784,6 +2220,33 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + [[package]] name = "motsu" version = "0.1.0-rc" @@ -808,6 +2271,23 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -837,6 +2317,45 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.1" @@ -846,6 +2365,56 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -872,12 +2441,50 @@ dependencies = [ "syn 1.0.109", ] +[[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 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.13" @@ -889,6 +2496,38 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" version = "0.10.2" @@ -899,6 +2538,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "portable-atomic" version = "1.9.0" @@ -975,7 +2620,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", @@ -987,11 +2632,31 @@ dependencies = [ "unarray", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pyth-stylus" version = "0.0.1" dependencies = [ - "alloy-primitives", + "alloy-primitives 0.7.6", "alloy-sol-types", "mini-alloc 0.4.2", "motsu", @@ -1058,6 +2723,47 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regalloc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + [[package]] name = "regex" version = "1.11.0" @@ -1087,6 +2793,66 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1098,24 +2864,54 @@ dependencies = [ ] [[package]] -name = "rlp" -version = "0.5.2" +name = "rkyv" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ + "bitvec", + "bytecheck", "bytes", - "rustc-hex", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid 1.10.0", ] [[package]] -name = "ruint" -version = "1.12.3" +name = "rkyv_derive" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", "bytes", "fastrlp", "num-bigint", @@ -1137,6 +2933,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -1167,13 +2969,34 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -1186,6 +3009,54 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.3" @@ -1200,6 +3071,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -1233,6 +3127,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -1244,6 +3149,39 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1266,256 +3204,873 @@ dependencies = [ ] [[package]] -name = "sha3-asm" -version = "0.1.4" +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if 1.0.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +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 = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.79", +] + +[[package]] +name = "stylus-proc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd02e91dffe7b73df84a861c992494d6b72054bc9a17fe73e147e34e9a64ef3" +dependencies = [ + "alloy-primitives 0.7.6", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26042693706e29fb7e3cf3d71c99534ac97fca98b6f81ba77ab658022ab2e210" +dependencies = [ + "alloy-primitives 0.7.6", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "mini-alloc 0.6.0", + "stylus-proc", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +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 = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[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 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "cc", - "cfg-if 1.0.0", + "getrandom", + "serde", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "uuid" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" [[package]] -name = "signature" -version = "2.2.0" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core", -] +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "spki" -version = "0.7.3" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "stylus-proc" -version = "0.6.0" +name = "wait-timeout" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd02e91dffe7b73df84a861c992494d6b72054bc9a17fe73e147e34e9a64ef3" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "cfg-if 1.0.0", - "convert_case 0.6.0", - "lazy_static", - "proc-macro2", - "quote", - "regex", - "sha3", - "syn 1.0.109", - "syn-solidity", + "libc", ] [[package]] -name = "stylus-sdk" -version = "0.6.0" +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26042693706e29fb7e3cf3d71c99534ac97fca98b6f81ba77ab658022ab2e210" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "cfg-if 1.0.0", - "derivative", - "hex", - "keccak-const", - "lazy_static", - "mini-alloc 0.6.0", - "stylus-proc", + "try-lock", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "syn" -version = "1.0.109" +name = "wasm-bindgen" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "cfg-if 1.0.0", + "once_cell", + "wasm-bindgen-macro", ] [[package]] -name = "syn" -version = "2.0.79" +name = "wasm-bindgen-backend" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ + "bumpalo", + "log", + "once_cell", "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.79", + "wasm-bindgen-shared", ] [[package]] -name = "syn-solidity" -version = "0.7.7" +name = "wasm-bindgen-downcast" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "5dac026d43bcca6e7ce1c0956ba68f59edf6403e8e930a5d891be72c31a44340" dependencies = [ - "paste", - "proc-macro2", - "quote", - "syn 2.0.79", + "js-sys", + "once_cell", + "wasm-bindgen", + "wasm-bindgen-downcast-macros", ] [[package]] -name = "tap" -version = "1.0.1" +name = "wasm-bindgen-downcast-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "c5020cfa87c7cecefef118055d44e3c1fc122c7ec25701d528ee458a0b45f38f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "tempfile" -version = "3.13.0" +name = "wasm-bindgen-futures" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if 1.0.0", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "thiserror" -version = "1.0.64" +name = "wasm-bindgen-macro" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ - "thiserror-impl", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "thiserror-impl" -version = "1.0.64" +name = "wasm-bindgen-macro-support" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", "syn 2.0.79", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "wasm-bindgen-shared" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] -name = "toml_datetime" -version = "0.6.8" +name = "wasm-encoder" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" +dependencies = [ + "leb128", + "wasmparser 0.219.1", +] [[package]] -name = "toml_edit" -version = "0.22.22" +name = "wasmer" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "78caedecd8cb71ed47ccca03b68d69414a3d278bb031e6f93f15759344efdd52" dependencies = [ - "indexmap", - "toml_datetime", - "winnow", + "bytes", + "cfg-if 1.0.0", + "derivative", + "indexmap 1.9.3", + "js-sys", + "more-asserts", + "rustc-demangle", + "serde", + "serde-wasm-bindgen", + "target-lexicon", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-downcast", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "wasmer-compiler" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "726a8450541af4a57c34af7b6973fdbfc79f896cc7e733429577dfd1d1687180" +dependencies = [ + "backtrace", + "cfg-if 1.0.0", + "enum-iterator", + "enumset", + "lazy_static", + "leb128", + "memmap2", + "more-asserts", + "region", + "smallvec", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.95.0", + "winapi", +] [[package]] -name = "ucd-trie" -version = "0.1.7" +name = "wasmer-compiler-cranelift" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "a1e5633f90f372563ebbdf3f9799c7b29ba11c90e56cf9b54017112d2e656c95" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "gimli 0.26.2", + "more-asserts", + "rayon", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", +] [[package]] -name = "uint" -version = "0.9.5" +name = "wasmer-derive" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "97901fdbaae383dbb90ea162cc3a76a9fa58ac39aec7948b4c0b9bbef9307738" dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "unarray" -version = "0.1.4" +name = "wasmer-types" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +checksum = "67f1f2839f4f61509550e4ddcd0e658e19f3af862b51c79fda15549d735d659b" +dependencies = [ + "bytecheck", + "enum-iterator", + "enumset", + "indexmap 1.9.3", + "more-asserts", + "rkyv", + "target-lexicon", + "thiserror", +] [[package]] -name = "unicode-ident" -version = "1.0.13" +name = "wasmer-vm" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "043118ec4f16d1714fed3aab758b502b864bd865e1d5188626c9ad290100563f" +dependencies = [ + "backtrace", + "cc", + "cfg-if 1.0.0", + "corosensei", + "dashmap", + "derivative", + "enum-iterator", + "fnv", + "indexmap 1.9.3", + "lazy_static", + "libc", + "mach", + "memoffset", + "more-asserts", + "region", + "scopeguard", + "thiserror", + "wasmer-types", + "winapi", +] [[package]] -name = "unicode-segmentation" -version = "1.12.0" +name = "wasmparser" +version = "0.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" +dependencies = [ + "indexmap 1.9.3", + "url", +] [[package]] -name = "valuable" -version = "0.1.0" +name = "wasmparser" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +dependencies = [ + "bitflags 2.6.0", + "indexmap 2.6.0", +] [[package]] -name = "version_check" -version = "0.9.5" +name = "wast" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] [[package]] -name = "wait-timeout" -version = "0.2.0" +name = "wat" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ - "libc", + "wast", ] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "web-sys" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "wee_alloc" @@ -1551,6 +4106,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1576,13 +4174,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1591,12 +4189,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + [[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.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1609,12 +4219,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + [[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.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1627,6 +4249,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 5f935507f1..d22b3276ff 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,7 +1,5 @@ [workspace] -members = [ - "contracts" -] +members = ["contracts", "examples/external-call-contracts","examples/function-calls"] default-members = [ "contracts", ] @@ -46,6 +44,17 @@ regex = "1.10.4" tiny-keccak = { version = "2.0.2", features = ["keccak"] } tokio = { version = "1.12.0", features = ["full"] } +alloy = { version = "0.1.4", features = [ + "contract", + "network", + "providers", + "provider-http", + "rpc-client", + "rpc-types-eth", + "signer-local", + "getrandom", +] } + # motsu test motsu = "=0.1.0-rc" @@ -54,6 +63,11 @@ pyth-stylus = { path = "contracts" } e2e = { path = "lib/e2e" } e2e-proc = {path = "lib/e2e-proc"} +# procedural macros +syn = { version = "2.0.58", features = ["full"] } +proc-macro2 = "1.0.79" +quote = "1.0.35" + [profile.release] codegen-units = 1 panic = "abort" diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml new file mode 100644 index 0000000000..725b9f9cd4 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "external-call-contracts" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +publish = false +version = "0.0.0" + +[dependencies] +alloy-primitives.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true + +[features] +e2e = [] diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml new file mode 100644 index 0000000000..575ef05b59 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "function-calls" +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs new file mode 100644 index 0000000000..e7a11a969c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From a1315932c3ab0a7325775c9ef9865abced1c47d5 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 23 Oct 2024 13:58:39 +0000 Subject: [PATCH 029/160] chore:fixed imports --- .../stylus/contracts/src/pyth/functions.rs | 20 ++++++------ .../sdk/stylus/contracts/src/pyth/ipyth.rs | 4 +-- .../sdk/stylus/contracts/src/pyth/mod.rs | 31 ++++++++++--------- .../sdk/stylus/contracts/src/pyth/solidity.rs | 7 ++--- 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index effa218b4b..70459f2ab1 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -18,53 +18,53 @@ use stylus_sdk::storage::TopLevelStorage; use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; -pub(crate) fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { +pub fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { let price_call = call_helper::(storage, pyth_address, (id,age,))?; Ok(price_call.price) } -pub(crate) fn get_update_fee(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result> { +pub fn get_update_fee(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result> { let update_fee_call = call_helper::(storage, pyth_address, (update_data,))?; Ok(update_fee_call.feeAmount) } -pub(crate) fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { +pub fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { let ema_price = call_helper::(storage, pyth_address, (id,))?; Ok(ema_price.price) } -pub(crate) fn get_ema_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { +pub fn get_ema_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { let ema_price = call_helper::(storage, pyth_address, (id,age,))?; Ok(ema_price.price) } -pub(crate) fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { +pub fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { let price = call_helper::(storage, pyth_address, (id,))?; Ok(price.price) } -pub(crate) fn get_valid_time_period(storage: &mut impl TopLevelStorage,pyth_address: Address) -> Result> { +pub fn get_valid_time_period(storage: &mut impl TopLevelStorage,pyth_address: Address) -> Result> { let valid_time_period = call_helper::(storage, pyth_address, ())?; Ok(valid_time_period.validTimePeriod) } -pub(crate) fn update_price_feeds(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result<(), Vec> { +pub fn update_price_feeds(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result<(), Vec> { delegate_call_helper::(storage, pyth_address, (update_data,))?; Ok(()) } -pub(crate) fn update_price_feeds_if_necessary(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Vec> { +pub fn update_price_feeds_if_necessary(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Vec> { delegate_call_helper::(storage, pyth_address, (update_data,price_ids, publish_times))?; Ok(()) } -pub(crate) fn parse_price_feed_updates(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { +pub fn parse_price_feed_updates(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; Ok(parse_price_feed_updates_call.priceFeeds) } -pub(crate) fn parse_price_feed_updates_unique(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { +pub fn parse_price_feed_updates_unique(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; Ok(parse_price_feed_updates_call.priceFeeds) } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs index 504c461080..973e932ce5 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs @@ -1,10 +1,8 @@ -use crate::pyth::solidity::{Price,PriceFeed}; -use alloc::vec::Vec; +use alloc::vec::Vec; use alloy_primitives::U256; use stylus_sdk::{abi::Bytes, alloy_primitives::FixedBytes}; pub trait IPyth { - type Error: Into>; fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 77d5775132..57b96f5fe7 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,10 +1,8 @@ use alloy_sol_types::SolValue; use ipyth::IPyth; -use solidity::Price; -use stylus_sdk::{abi::Bytes as AbiBytes, block, prelude::*, storage::TopLevelStorage}; -use errors::{Error, FalledDecodeData, StalePrice}; +use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; use alloc::vec::Vec; -use alloy_primitives::{ Address, FixedBytes, U256 , Bytes }; +use alloy_primitives::{ FixedBytes, U256 , Bytes }; use functions::{ get_price_unsafe, get_price_no_older_than, @@ -21,8 +19,8 @@ mod errors; mod events; mod ipyth; mod solidity; -mod mock; -mod functions; +pub mod mock; +pub mod functions; sol_storage! { @@ -62,11 +60,19 @@ impl IPyth for PythContract { Ok(data) } + fn get_update_fee(&mut self, update_data: Vec) -> Result { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let fee = get_update_fee(self, self._ipyth.get(), data)?; + Ok(fee) + } + + #[payable] fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); update_price_feeds(self, self._ipyth.get(),data) } - + + #[payable] fn update_price_feeds_if_necessary( &mut self, update_data: Vec, @@ -77,12 +83,7 @@ impl IPyth for PythContract { update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times) } - fn get_update_fee(&mut self, update_data: Vec) -> Result { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let fee = get_update_fee(self, self._ipyth.get(), data)?; - Ok(fee) - } - + #[payable] fn parse_price_feed_updates( &mut self, update_data: Vec, @@ -95,7 +96,7 @@ impl IPyth for PythContract { Ok(encode_data) } - + #[payable] fn parse_price_feed_updates_unique( &mut self, update_data: Vec, @@ -104,7 +105,7 @@ impl IPyth for PythContract { max_publish_time: u64, ) -> Result, Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + let encode_data= parse_price_feed_updates_unique(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); Ok(encode_data) } } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs index b7eaf4fc0f..55ea34a9e2 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs @@ -1,8 +1,5 @@ - -use alloc::string::ToString; -use alloy_primitives::{hex::encode, Signed, I32, I64, U256, U32, U64}; -use alloy_sol_types::SolValue; -use stylus_sdk::{alloy_sol_types::sol, prelude::*, ArbResult,abi::internal::EncodableReturnType}; +use alloy_primitives::{ I32, I64, U64}; +use stylus_sdk::{alloy_sol_types::sol, prelude::*}; sol_storage! { From 299b0cea467804b6831f7f90d57ee57989a81b29 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 23 Oct 2024 15:39:16 +0000 Subject: [PATCH 030/160] chore:fixed types --- target_chains/ethereum/sdk/stylus/Cargo.lock | 181 +++++++-------- target_chains/ethereum/sdk/stylus/Cargo.toml | 49 ++-- .../ethereum/sdk/stylus/contracts/src/lib.rs | 9 +- .../sdk/stylus/contracts/src/pyth/types.rs | 214 ++++++++++++++++++ .../ethereum/sdk/stylus/rust-toolchain.toml | 2 - 5 files changed, 335 insertions(+), 120 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs delete mode 100644 target_chains/ethereum/sdk/stylus/rust-toolchain.toml diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 71fbab1cc0..6dfb40f852 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -78,11 +78,11 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.38" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156bfc5dcd52ef9a5f33381701fa03310317e14c65093a9430d3e3557b08dcd3" +checksum = "d4932d790c723181807738cf1ac68198ab581cd699545b155601332541ee47bd" dependencies = [ - "alloy-primitives 0.8.8", + "alloy-primitives 0.8.9", "num_enum", "strum", ] @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f35429a652765189c1c5092870d8360ee7b7769b09b06d89ebaefd34676446" +checksum = "c71738eb20c42c5fb149571e76536a0f309d142f3957c28791662b96baf77a3d" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -311,7 +311,7 @@ checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -417,7 +417,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -434,7 +434,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "syn-solidity", "tiny-keccak", ] @@ -452,7 +452,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.79", + "syn 2.0.82", "syn-solidity", ] @@ -710,7 +710,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -721,7 +721,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -732,7 +732,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -896,9 +896,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -926,9 +926,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -986,7 +986,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1238,7 +1238,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1249,7 +1249,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1296,7 +1296,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1316,7 +1316,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "unicode-xid", ] @@ -1367,7 +1367,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1447,7 +1447,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1496,7 +1496,7 @@ dependencies = [ "alloy-primitives 0.7.6", "e2e", "eyre", - "mini-alloc 0.4.2", + "mini-alloc", "stylus-sdk", "tokio", ] @@ -1594,7 +1594,17 @@ dependencies = [ [[package]] name = "function-calls" -version = "0.0.1" +version = "0.0.0" +dependencies = [ + "alloy", + "alloy-primitives 0.7.6", + "e2e", + "eyre", + "mini-alloc", + "pyth-stylus", + "stylus-sdk", + "tokio", +] [[package]] name = "funty" @@ -1658,7 +1668,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -1866,9 +1876,9 @@ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -2106,9 +2116,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" @@ -2211,15 +2221,6 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "mini-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14eacc4bfcf10da9b6d5185ef51b2dc75434afac34b4d8aabe40f6b141ee071c" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -2268,7 +2269,7 @@ checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2344,7 +2345,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2358,18 +2359,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", @@ -2388,7 +2386,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2399,9 +2397,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -2487,9 +2485,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -2513,7 +2511,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -2544,12 +2542,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2605,9 +2597,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -2658,7 +2650,7 @@ version = "0.0.1" dependencies = [ "alloy-primitives 0.7.6", "alloy-sol-types", - "mini-alloc 0.4.2", + "mini-alloc", "motsu", "stylus-sdk", ] @@ -2879,7 +2871,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -2987,15 +2979,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rusty-fork" @@ -3120,9 +3112,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] @@ -3140,20 +3132,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -3322,7 +3314,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3357,7 +3349,6 @@ dependencies = [ "hex", "keccak-const", "lazy_static", - "mini-alloc 0.6.0", "stylus-proc", ] @@ -3380,9 +3371,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -3398,7 +3389,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3437,22 +3428,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3490,9 +3481,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -3514,7 +3505,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3633,7 +3624,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -3749,9 +3740,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "valuable" @@ -3817,7 +3808,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "wasm-bindgen-shared", ] @@ -3874,7 +3865,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4297,7 +4288,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] [[package]] @@ -4317,5 +4308,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.82", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index d22b3276ff..863100a51e 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = ["contracts", "examples/external-call-contracts","examples/function-calls"] default-members = [ - "contracts", + "contracts", "examples/external-call-contracts","examples/function-calls" ] # Explicitly set the resolver to version 2, which is the default for packages @@ -27,24 +27,11 @@ pedantic = "warn" all = "warn" [workspace.dependencies] -alloy-primitives = "=0.7.6" -alloy-sol-types = "=0.7.6" +# stylus-related +stylus-sdk = { version = "=0.6.0", default-features = false } mini-alloc = "0.4.2" -stylus-sdk = "0.6.0" -stylus-proc = "0.6.0" -hex = "0.4.3" -dotenv = "0.15.0" -const-hex = { version = "1.11.1", default-features = false } -eyre = "0.6.8" -keccak-const = "0.2.0" -koba = "0.2.0" -once_cell = "1.19.0" -rand = "0.8.5" -regex = "1.10.4" -tiny-keccak = { version = "2.0.2", features = ["keccak"] } -tokio = { version = "1.12.0", features = ["full"] } -alloy = { version = "0.1.4", features = [ +alloy = { version = "=0.1.4", features = [ "contract", "network", "providers", @@ -54,6 +41,30 @@ alloy = { version = "0.1.4", features = [ "signer-local", "getrandom", ] } +# Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need +# to keep both versions for compatibility with the Stylus SDK. Once they start +# using `alloy` we can remove these. +alloy-primitives = { version = "=0.7.6", default-features = false } +alloy-sol-types = { version = "=0.7.6", default-features = false } +alloy-sol-macro = { version = "=0.7.6", default-features = false } +alloy-sol-macro-expander = { version = "=0.7.6", default-features = false } +alloy-sol-macro-input = { version = "=0.7.6", default-features = false } + +const-hex = { version = "1.11.1", default-features = false } +eyre = "0.6.8" +keccak-const = "0.2.0" +koba = "0.2.0" +once_cell = "1.19.0" +rand = "0.8.5" +regex = "1.10.4" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } +tokio = { version = "1.12.0", features = ["full"] } +futures = "0.3.30" + +# procedural macros +syn = { version = "2.0.58", features = ["full"] } +proc-macro2 = "1.0.79" +quote = "1.0.35" # motsu test motsu = "=0.1.0-rc" @@ -63,10 +74,6 @@ pyth-stylus = { path = "contracts" } e2e = { path = "lib/e2e" } e2e-proc = {path = "lib/e2e-proc"} -# procedural macros -syn = { version = "2.0.58", features = ["full"] } -proc-macro2 = "1.0.79" -quote = "1.0.35" [profile.release] codegen-units = 1 diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 01a47b1255..969a2e7f8e 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -4,10 +4,15 @@ extern crate alloc; -// #[global_allocator] -// static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +#[global_allocator] + static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod utils; pub mod pyth; +#[cfg(target_arch = "wasm32")] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs new file mode 100644 index 0000000000..dc31edfb5b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -0,0 +1,214 @@ +//This Module implements StoragePrice and StoragePriceFeed Structs. +// +// These structs represent price and price feed data in as recived fron pyth sdk . +// +// `StoragePrice` is used to store the essential details of a price: the value, confidence interval, exponent, and publish time. +// +// `StoragePriceFeed` represents a complete price feed that includes a unique identifier (ID) and two price structures: the current price and the exponentially moving average (EMA) price. +// +// Note that these structures are not functional on their own. You need to implement additional logic to properly interact with the price feed data in your contracts. +// +// Key Features: +// - `StoragePrice`: stores a single price and its metadata. +// - `StoragePriceFeed`: manages a feed of price data, with both current and EMA prices. +// +// Methods: +// - `to_price`: Converts a `StoragePrice` to a more generic `Price` structure. +// - `set`: Sets the values of `StoragePrice` or `StoragePriceFeed` using the provided `Price` or `PriceFeed` data. +// +// These methods ensure data consistency between the storage-optimized structs and the higher-level structures that will be used in external functions. + +use alloy_primitives::{ I32, I64, U64}; +use stylus_sdk::{alloy_sol_types::sol, prelude::*}; + +sol_storage !{ + pub struct StoragePrice { + int64 price; + uint64 conf; + int32 expo; + uint publish_time; + } + + pub struct StoragePriceFeed { + bytes32 id; + StoragePrice price; + StoragePrice ema_price; + } +} + + +sol! { + /// Represents a price structure containing the current price data. + /// + /// # Fields + /// - `price`: The current price value as an `int64`. + /// - `conf`: The confidence level of the price as a `uint64`. + /// - `expo`: The exponent value indicating the scale of the price as an `int32`. + /// - `publish_time`: The timestamp of when the price was published as a `uint`. + #[derive(Debug, Copy)] + struct Price { + int64 price; + uint64 conf; + int32 expo; + uint publish_time; + } + + /// Represents a price feed structure containing an ID and associated price data. + /// + /// This struct is used solely as a return type for function selectors related + /// to price feeds in the SDK. + /// + /// # Fields + /// - `id`: The unique identifier for the price feed as a `bytes32`. + /// - `price`: The current price information as a `Price`. + /// - `ema_price`: The Exponential Moving Average (EMA) price information as a `Price`. + #[derive(Debug, Copy)] + struct PriceFeed { + bytes32 id; + Price price; + Price ema_price; + } + + // Function call selector: Fetches the price associated with the given ID without validation. + // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + function getPriceUnsafe(bytes32 id) external view returns (Price memory price); + + // Function call selector: Retrieves the price associated with the given ID + // ensuring that the price is not older than the specified age in seconds. + // Returns the `Price` data for the specified `bytes32` ID. + function getPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (Price memory price); + + // Function call selector: Fetches the Exponential Moving Average (EMA) price + // associated with the given ID without validation. + // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + function getEmaPriceUnsafe( + bytes32 id + ) external view returns (Price memory price); + + // Function call selector: Retrieves the EMA price associated with the given ID + // ensuring that the price is not older than the specified age in seconds. + // Returns the `Price` data for the specified `bytes32` ID. + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (Price memory price); + + // Function call selector: Updates multiple price feeds using the provided update data. + // Accepts an array of `bytes` containing update data and processes the updates. + function updatePriceFeeds(bytes[] calldata updateData) external payable; + + // Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. + // Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds + // and processes the updates if conditions are met. + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + // Function call selector: Calculates the fee amount required to update price feeds + // based on the provided update data. + // Returns the fee amount as a `uint`. + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + + // Function call selector: Parses updates from the provided data for multiple price feeds. + // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + // and returns an array of `PriceFeed` structs containing the parsed updates. + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Parses updates from the provided data for unique price feeds. + // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + // and returns an array of unique `PriceFeed` structs containing the parsed updates. + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Queries the price feed for a given ID. + // Returns an array of `PriceFeed` structs associated with the specified `bytes32` ID. + function queryPriceFeed( + bytes32 id + ) public view virtual returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Checks if a price feed exists for the given ID. + // Returns an array of `PriceFeed` structs if it exists for the specified `bytes32` ID. + function priceFeedExists( + bytes32 id + ) public view virtual returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Retrieves the valid time period for price feeds. + // Returns the valid time period as a `uint`. + function getValidTimePeriod() + public + view + virtual + returns (uint validTimePeriod); + +} + +impl StoragePrice { + // Converts the `StoragePrice` instance into a `Price` struct. + // + // This method retrieves the stored values from the `StoragePrice` + // and creates a `Price` struct, making it suitable for external + // use or return types in function calls. + pub fn to_price(&self) -> Price { + Price { + price: self.price.get().as_i64(), + conf: self.conf.get().to(), + expo: self.expo.get().as_i32(), + publish_time: self.publish_time.get(), + } + } + + // Sets the values of the `StoragePrice` instance from a given `Price` struct. + // This method updates the stored values in the `StoragePrice` with + // the corresponding values from the provided `Price` struct, ensuring + // that the internal state is accurately reflected. + pub fn set(&mut self, price: Price) { + self.price.set(I64::try_from(price.price).unwrap()); + self.conf.set(U64::try_from(price.conf).unwrap()); + self.expo.set(I32::try_from(price.expo).unwrap()); + self.publish_time.set(price.publish_time); + } +} + +impl StoragePriceFeed { + // Converts the `StoragePriceFeed` instance into a `PriceFeed` struct. + // + // This method retrieves the stored price feed values and creates + // a `PriceFeed` struct, which includes the unique identifier and + // associated price data, making it suitable for external use or + // return types in function calls. + pub fn to_price_feed(&self) -> PriceFeed { + PriceFeed { + id: self.id.get(), + price: self.price.to_price(), + ema_price: self.ema_price.to_price(), + } + } + + // Sets the values of the `StoragePriceFeed` instance from a given `PriceFeed` struct. + // + // This method updates the stored values in the `StoragePriceFeed` + // with the corresponding values from the provided `PriceFeed` struct, + // ensuring that the internal state is accurately reflected. + pub fn set(&mut self, price_feed: PriceFeed) { + self.id.set(price_feed.id); + self.price.set(price_feed.price); + self.ema_price.set(price_feed.ema_price); + } +} + diff --git a/target_chains/ethereum/sdk/stylus/rust-toolchain.toml b/target_chains/ethereum/sdk/stylus/rust-toolchain.toml deleted file mode 100644 index 4d2dee853e..0000000000 --- a/target_chains/ethereum/sdk/stylus/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.80.0" From 7ac267f84d9ff51d7398094ecd4d030af54852fc Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 23 Oct 2024 17:24:33 +0000 Subject: [PATCH 031/160] completed contract --- .../stylus/contracts/src/pyth/functions.rs | 2 +- .../sdk/stylus/contracts/src/pyth/mock.rs | 18 +-- .../sdk/stylus/contracts/src/pyth/mod.rs | 111 +------------- .../contracts/src/pyth/pyth_contract.rs | 103 +++++++++++++ .../sdk/stylus/contracts/src/pyth/solidity.rs | 143 ------------------ 5 files changed, 113 insertions(+), 264 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index 70459f2ab1..ff7e144343 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -1,4 +1,4 @@ -use crate::pyth::solidity::{ +use crate::pyth::types::{ getEmaPriceUnsafeCall, getEmaPriceNoOlderThanCall, getPriceUnsafeCall, diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 3118fef9db..6d044fb462 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -1,14 +1,11 @@ -use core::borrow::BorrowMut; - use alloc::vec::Vec; -use alloy_primitives::{Uint, U64}; -use alloy_sol_types::{abi, sol_data::Uint as SolUInt, SolType, SolValue}; +use alloy_primitives::Uint; +use alloy_sol_types::{ sol_data::Uint as SolUInt, SolType, SolValue}; use stylus_sdk::{abi::Bytes, alloy_primitives::{FixedBytes, U256}, evm, msg, prelude::*}; use crate::{pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE}; use crate::pyth::errors::{Error, PriceFeedNotFound}; -use crate::pyth::solidity::{ PriceFeed,Price, StoragePriceFeed}; +use crate::pyth::types::{ PriceFeed,Price, StoragePriceFeed}; use crate::pyth::events::PriceFeedUpdate; -use crate::pyth::ipyth::IPyth; //decode data type pub(crate) type DecodeDataType = (PriceFeed, SolUInt<64>); @@ -95,7 +92,7 @@ impl MockPythContract { fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { self.single_update_fee_in_wei.get() * U256::from(update_data.len()) } - + #[payable] fn parse_price_feed_updates( &mut self, update_data:Vec, @@ -106,7 +103,7 @@ impl MockPythContract { self.parse_price_feed_updates_internal(update_data, price_ids, min_publish_time, max_publish_time, false) } - + #[payable] fn parse_price_feed_updates_unique( &mut self, update_data:Vec, @@ -207,7 +204,7 @@ mod tests { use stylus_sdk::{abi::Bytes, contract, msg::{self, value}}; use crate::pyth::{ PythContract, mock::{MockPythContract, DecodeDataType}, - errors::{Error, InvalidArgument}, solidity::{PriceFeed, Price, StoragePriceFeed} + errors::{Error, InvalidArgument}, types::{PriceFeed, Price, StoragePriceFeed} }; use alloy_sol_types::SolType; @@ -298,8 +295,5 @@ mod tests { let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, U256::from(1000), PREV_PUBLISH_TIME); let mut update_data: Vec = vec![]; update_data.push(Bytes::from(price_feed_created)); - //let balance = contrac; - info!("{:?} ", msg::sender()); - //contract.update_price_feeds(update_data) } } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 57b96f5fe7..fcbd826300 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,114 +1,9 @@ -use alloy_sol_types::SolValue; -use ipyth::IPyth; -use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; -use alloc::vec::Vec; -use alloy_primitives::{ FixedBytes, U256 , Bytes }; -use functions::{ - get_price_unsafe, - get_price_no_older_than, - get_ema_price_unsafe, - get_ema_price_no_older_than, - update_price_feeds, - update_price_feeds_if_necessary, - get_update_fee, - parse_price_feed_updates, - parse_price_feed_updates_unique -}; - mod errors; mod events; mod ipyth; -mod solidity; -pub mod mock; +mod types; +mod mock; pub mod functions; - - -sol_storage! { - struct PythContract { - address _ipyth; - } -} - -unsafe impl TopLevelStorage for PythContract {} - -#[public] -impl IPyth for PythContract { - - type Error = Vec; - - fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { - let price = get_price_unsafe(self, self._ipyth.get(), id)?; - let data = price.abi_encode(); - Ok(data) - } - - fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error> { - let price = get_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; - let data = price.abi_encode(); - Ok(data) - } - - fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { - let price = get_ema_price_unsafe(self, self._ipyth.get(), id)?; - let data = price.abi_encode(); - Ok(data) - } - - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error> { - let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; - let data = price.abi_encode(); - Ok(data) - } - - fn get_update_fee(&mut self, update_data: Vec) -> Result { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let fee = get_update_fee(self, self._ipyth.get(), data)?; - Ok(fee) - } - - #[payable] - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds(self, self._ipyth.get(),data) - } - - #[payable] - fn update_price_feeds_if_necessary( - &mut self, - update_data: Vec, - price_ids: Vec>, - publish_times: Vec, - ) -> Result<(), Self::Error> { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times) - } - - #[payable] - fn parse_price_feed_updates( - &mut self, - update_data: Vec, - price_ids: Vec>, - min_publish_time: u64, - max_publish_time: u64, - ) -> Result, Self::Error> { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); - Ok(encode_data) - } - - #[payable] - fn parse_price_feed_updates_unique( - &mut self, - update_data: Vec, - price_ids: Vec>, - min_publish_time: u64, - max_publish_time: u64, - ) -> Result, Self::Error> { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let encode_data= parse_price_feed_updates_unique(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); - Ok(encode_data) - } -} - +pub mod pyth_contract; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs new file mode 100644 index 0000000000..22cf355f41 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -0,0 +1,103 @@ +use alloy_sol_types::SolValue; +use crate::pyth::ipyth::IPyth; +use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; +use alloc::vec::Vec; +use alloy_primitives::{ FixedBytes, U256 , Bytes }; +use crate::pyth::functions::{ + get_price_unsafe, + get_price_no_older_than, + get_ema_price_unsafe, + get_ema_price_no_older_than, + update_price_feeds, + update_price_feeds_if_necessary, + get_update_fee, + parse_price_feed_updates, + parse_price_feed_updates_unique +}; + +sol_storage! { + struct PythContract { + address _ipyth; + } +} + +unsafe impl TopLevelStorage for PythContract {} + +#[public] +impl IPyth for PythContract { + + type Error = Vec; + + fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + let price = get_price_unsafe(self, self._ipyth.get(), id)?; + let data = price.abi_encode(); + Ok(data) + } + + fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error> { + let price = get_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + let data = price.abi_encode(); + Ok(data) + } + + fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + let price = get_ema_price_unsafe(self, self._ipyth.get(), id)?; + let data = price.abi_encode(); + Ok(data) + } + + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error> { + let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + let data = price.abi_encode(); + Ok(data) + } + + fn get_update_fee(&mut self, update_data: Vec) -> Result { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let fee = get_update_fee(self, self._ipyth.get(), data)?; + Ok(fee) + } + + #[payable] + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + update_price_feeds(self, self._ipyth.get(),data) + } + + #[payable] + fn update_price_feeds_if_necessary( + &mut self, + update_data: Vec, + price_ids: Vec>, + publish_times: Vec, + ) -> Result<(), Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times) + } + + #[payable] + fn parse_price_feed_updates( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + Ok(encode_data) + } + + #[payable] + fn parse_price_feed_updates_unique( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error> { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let encode_data= parse_price_feed_updates_unique(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + Ok(encode_data) + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs deleted file mode 100644 index 55ea34a9e2..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/solidity.rs +++ /dev/null @@ -1,143 +0,0 @@ -use alloy_primitives::{ I32, I64, U64}; -use stylus_sdk::{alloy_sol_types::sol, prelude::*}; - - -sol_storage! { - pub struct StoragePrice { - // Price - int64 price; - // Confidence interval around the price - uint64 conf; - // Price exponent - int32 expo; - // Unix timestamp describing when the price was published - uint publish_time; - } - - pub struct StoragePriceFeed { - bytes32 id; - StoragePrice price; - StoragePrice ema_price; - } -} - - -sol! { - #[derive(Debug, Copy)] - struct Price { - int64 price; - uint64 conf; - int32 expo; - uint publish_time; - } - - #[derive(Debug, Copy)] - struct PriceFeed { - bytes32 id; - Price price; - Price ema_price; - } - - function getPriceUnsafe(bytes32 id) external view returns (Price memory price); - - - function getPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (Price memory price); - - - function getEmaPriceUnsafe( - bytes32 id - ) external view returns (Price memory price) ; - - function getEmaPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (Price memory price) ; - - - function updatePriceFeeds(bytes[] calldata updateData) external payable; - - function updatePriceFeedsIfNecessary( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64[] calldata publishTimes - ) external payable; - - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount); - - function parsePriceFeedUpdates( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds) ; - - - function parsePriceFeedUpdatesUnique( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds); - - - function queryPriceFeed( - bytes32 id - ) public view virtual returns (PriceFeed[] memory priceFeeds); - - function priceFeedExists( - bytes32 id - ) public view virtual returns (PriceFeed[] memory priceFeeds); - - - function getValidTimePeriod() - public - view - virtual - returns (uint validTimePeriod); - -} - - - -impl StoragePrice { - pub fn to_price(&self) ->Price { - Price { - price:self.price.get().as_i64(), - conf: self.conf.get().to(), - expo: self.expo.get().as_i32(), - publish_time: self.publish_time.get() - } - } - - pub fn set(&mut self, price:Price) { - self.price.set(I64::try_from(price.price).unwrap()); - self.conf.set(U64::try_from(price.conf).unwrap()); - self.expo.set(I32::try_from(price.expo).unwrap()); - self.publish_time.set(price.publish_time); - } - -} - -impl StoragePriceFeed { - pub fn to_price_feed(&self)-> PriceFeed { - PriceFeed { - id: self.id.get(), - price: self.price.to_price(), - ema_price: self.ema_price.to_price() - } - } - - pub fn set(&mut self, price_feed:PriceFeed) { - self.id.set(price_feed.id); - self.price.set(price_feed.price); - self.ema_price.set(price_feed.ema_price); - } - -} - -//impl for Price {} From 41e30099460dee08a9c9618b4ab7ee85f5f96e40 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 23 Oct 2024 17:25:02 +0000 Subject: [PATCH 032/160] External call and fution call sample --- .../external-call-contracts/src/lib.rs | 0 .../external-call-contracts/src/main.rs | 3 - .../stylus/examples/function-calls/Cargo.toml | 20 +- .../pyth-sdk-solidity/AbstractPyth.sol | 138 ++++ .../@pythnetwork/pyth-sdk-solidity/IPyth.sol | 141 ++++ .../pyth-sdk-solidity/IPythEvents.sol | 18 + .../@pythnetwork/pyth-sdk-solidity/LICENSE | 13 + .../pyth-sdk-solidity/MockPyth.sol | 189 +++++ .../pyth-sdk-solidity/PythAggregatorV3.sol | 117 +++ .../pyth-sdk-solidity/PythErrors.sol | 48 ++ .../pyth-sdk-solidity/PythStructs.sol | 33 + .../pyth-sdk-solidity/PythUtils.sol | 34 + .../@pythnetwork/pyth-sdk-solidity/README.md | 103 +++ .../pyth-sdk-solidity/abis/AbstractPyth.json | 661 ++++++++++++++++ .../pyth-sdk-solidity/abis/IPyth.json | 452 +++++++++++ .../pyth-sdk-solidity/abis/IPythEvents.json | 33 + .../pyth-sdk-solidity/abis/MockPyth.json | 746 ++++++++++++++++++ .../pyth-sdk-solidity/abis/PythErrors.json | 72 ++ .../pyth-sdk-solidity/abis/PythUtils.json | 31 + .../pyth-sdk-solidity/package.json | 34 + .../examples/function-calls/src/MockPyth.sol | 44 ++ .../stylus/examples/function-calls/src/lib.rs | 24 + .../examples/function-calls/src/main.rs | 3 - .../examples/function-calls/tests/abi/mod.rs | 64 ++ .../function-calls/tests/function_call.rs | 25 + 25 files changed, 3038 insertions(+), 8 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs deleted file mode 100644 index e7a11a969c..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index 575ef05b59..9d72c09cca 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -4,7 +4,23 @@ authors.workspace = true license.workspace = true keywords.workspace = true repository.workspace = true -version.workspace = true -edition.workspace = true +publish = false +version = "0.0.0" [dependencies] +alloy-primitives.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true +pyth-stylus.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true + +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol new file mode 100644 index 0000000000..691b4804c3 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "./PythStructs.sol"; +import "./IPyth.sol"; +import "./PythErrors.sol"; + +abstract contract AbstractPyth is IPyth { + /// @notice Returns the price feed with given id. + /// @dev Reverts if the price does not exist. + /// @param id The Pyth Price Feed ID of which to fetch the PriceFeed. + function queryPriceFeed( + bytes32 id + ) public view virtual returns (PythStructs.PriceFeed memory priceFeed); + + /// @notice Returns true if a price feed with the given id exists. + /// @param id The Pyth Price Feed ID of which to check its existence. + function priceFeedExists( + bytes32 id + ) public view virtual returns (bool exists); + + /// @notice This function is deprecated and is only kept for backward compatibility. + function getValidTimePeriod() + public + view + virtual + returns (uint validTimePeriod); + + /// @notice This function is deprecated and is only kept for backward compatibility. + function getPrice( + bytes32 id + ) external view virtual returns (PythStructs.Price memory price) { + return getPriceNoOlderThan(id, getValidTimePeriod()); + } + + /// @notice This function is deprecated and is only kept for backward compatibility. + function getEmaPrice( + bytes32 id + ) external view virtual returns (PythStructs.Price memory price) { + return getEmaPriceNoOlderThan(id, getValidTimePeriod()); + } + + function getPriceUnsafe( + bytes32 id + ) public view virtual override returns (PythStructs.Price memory price) { + PythStructs.PriceFeed memory priceFeed = queryPriceFeed(id); + return priceFeed.price; + } + + function getPriceNoOlderThan( + bytes32 id, + uint age + ) public view virtual override returns (PythStructs.Price memory price) { + price = getPriceUnsafe(id); + + if (diff(block.timestamp, price.publishTime) > age) + revert PythErrors.StalePrice(); + + return price; + } + + function getEmaPriceUnsafe( + bytes32 id + ) public view virtual override returns (PythStructs.Price memory price) { + PythStructs.PriceFeed memory priceFeed = queryPriceFeed(id); + return priceFeed.emaPrice; + } + + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) public view virtual override returns (PythStructs.Price memory price) { + price = getEmaPriceUnsafe(id); + + if (diff(block.timestamp, price.publishTime) > age) + revert PythErrors.StalePrice(); + + return price; + } + + function diff(uint x, uint y) internal pure returns (uint) { + if (x > y) { + return x - y; + } else { + return y - x; + } + } + + // Access modifier is overridden to public to be able to call it locally. + function updatePriceFeeds( + bytes[] calldata updateData + ) public payable virtual override; + + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable virtual override { + if (priceIds.length != publishTimes.length) + revert PythErrors.InvalidArgument(); + + for (uint i = 0; i < priceIds.length; i++) { + if ( + !priceFeedExists(priceIds[i]) || + queryPriceFeed(priceIds[i]).price.publishTime < publishTimes[i] + ) { + updatePriceFeeds(updateData); + return; + } + } + + revert PythErrors.NoFreshUpdate(); + } + + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) + external + payable + virtual + override + returns (PythStructs.PriceFeed[] memory priceFeeds); + + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) + external + payable + virtual + override + returns (PythStructs.PriceFeed[] memory priceFeeds); +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol new file mode 100644 index 0000000000..c94e8c3ed8 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "./PythStructs.sol"; +import "./IPythEvents.sol"; + +/// @title Consume prices from the Pyth Network (https://pyth.network/). +/// @dev Please refer to the guidance at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how to consume prices safely. +/// @author Pyth Data Association +interface IPyth is IPythEvents { + /// @notice Returns the price of a price feed without any sanity checks. + /// @dev This function returns the most recent price update in this contract without any recency checks. + /// This function is unsafe as the returned price update may be arbitrarily far in the past. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use `getPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPriceUnsafe( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the price that is no older than `age` seconds of the current time. + /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. + /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. + /// However, if the price is not recent this function returns the latest available price. + /// + /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that + /// the returned price is recent or useful for any particular application. + /// + /// Users of this function should check the `publishTime` in the price to ensure that the returned price is + /// sufficiently recent for their application. If you are considering using this function, it may be + /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceUnsafe( + bytes32 id + ) external view returns (PythStructs.Price memory price); + + /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds + /// of the current time. + /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in + /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently + /// recently. + /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (PythStructs.Price memory price); + + /// @notice Update price feeds with given update messages. + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// Prices will be updated if they are more recent than the current stored prices. + /// The call will succeed even if the update is not the most recent. + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + function updatePriceFeeds(bytes[] calldata updateData) external payable; + + /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is + /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the + /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime + /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have + /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. + /// Otherwise, it calls updatePriceFeeds method to update the prices. + /// + /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + /// @notice Returns the required fee to update an array of price updates. + /// @param updateData Array of price update data. + /// @return feeAmount The required fee in Wei. + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + + /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published + /// within `minPublishTime` and `maxPublishTime`. + /// + /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; + /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they + /// are more recent than the current stored prices. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); + + /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are + /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, + /// this method will return the first update. This method may store the price updates on-chain, if they + /// are more recent than the current stored prices. + /// + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range and uniqueness condition. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol new file mode 100644 index 0000000000..a293776162 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +/// @title IPythEvents contains the events that Pyth contract emits. +/// @dev This interface can be used for listening to the updates for off-chain and testing purposes. +interface IPythEvents { + /// @dev Emitted when the price feed with `id` has received a fresh update. + /// @param id The Pyth Price Feed ID. + /// @param publishTime Publish time of the given price update. + /// @param price Price of the given price update. + /// @param conf Confidence interval of the given price update. + event PriceFeedUpdate( + bytes32 indexed id, + uint64 publishTime, + int64 price, + uint64 conf + ); +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE new file mode 100644 index 0000000000..92f6c4365f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE @@ -0,0 +1,13 @@ +Copyright 2024 Pyth Data Association. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol new file mode 100644 index 0000000000..dcb8d7864a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "./AbstractPyth.sol"; +import "./PythStructs.sol"; +import "./PythErrors.sol"; + +contract MockPyth is AbstractPyth { + mapping(bytes32 => PythStructs.PriceFeed) priceFeeds; + + uint singleUpdateFeeInWei; + uint validTimePeriod; + + constructor(uint _validTimePeriod, uint _singleUpdateFeeInWei) { + singleUpdateFeeInWei = _singleUpdateFeeInWei; + validTimePeriod = _validTimePeriod; + } + + function queryPriceFeed( + bytes32 id + ) public view override returns (PythStructs.PriceFeed memory priceFeed) { + if (priceFeeds[id].id == 0) revert PythErrors.PriceFeedNotFound(); + return priceFeeds[id]; + } + + function priceFeedExists(bytes32 id) public view override returns (bool) { + return (priceFeeds[id].id != 0); + } + + function getValidTimePeriod() public view override returns (uint) { + return validTimePeriod; + } + + // Takes an array of encoded price feeds and stores them. + // You can create this data either by calling createPriceFeedUpdateData or + // by using web3.js or ethers abi utilities. + // @note: The updateData expected here is different from the one used in the main contract. + // In particular, the expected format is: + // [ + // abi.encode( + // PythStructs.PriceFeed( + // bytes32 id, + // PythStructs.Price price, + // PythStructs.Price emaPrice + // ), + // uint64 prevPublishTime + // ) + // ] + function updatePriceFeeds( + bytes[] calldata updateData + ) public payable override { + uint requiredFee = getUpdateFee(updateData); + if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); + + for (uint i = 0; i < updateData.length; i++) { + PythStructs.PriceFeed memory priceFeed = abi.decode( + updateData[i], + (PythStructs.PriceFeed) + ); + + uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime; + + if (lastPublishTime < priceFeed.price.publishTime) { + // Price information is more recent than the existing price information. + priceFeeds[priceFeed.id] = priceFeed; + emit PriceFeedUpdate( + priceFeed.id, + uint64(priceFeed.price.publishTime), + priceFeed.price.price, + priceFeed.price.conf + ); + } + } + } + + function getUpdateFee( + bytes[] calldata updateData + ) public view override returns (uint feeAmount) { + return singleUpdateFeeInWei * updateData.length; + } + + function parsePriceFeedUpdatesInternal( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime, + bool unique + ) internal returns (PythStructs.PriceFeed[] memory feeds) { + uint requiredFee = getUpdateFee(updateData); + if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); + + feeds = new PythStructs.PriceFeed[](priceIds.length); + + for (uint i = 0; i < priceIds.length; i++) { + for (uint j = 0; j < updateData.length; j++) { + uint64 prevPublishTime; + (feeds[i], prevPublishTime) = abi.decode( + updateData[j], + (PythStructs.PriceFeed, uint64) + ); + + uint publishTime = feeds[i].price.publishTime; + if (priceFeeds[feeds[i].id].price.publishTime < publishTime) { + priceFeeds[feeds[i].id] = feeds[i]; + emit PriceFeedUpdate( + feeds[i].id, + uint64(publishTime), + feeds[i].price.price, + feeds[i].price.conf + ); + } + + if (feeds[i].id == priceIds[i]) { + if ( + minPublishTime <= publishTime && + publishTime <= maxPublishTime && + (!unique || prevPublishTime < minPublishTime) + ) { + break; + } else { + feeds[i].id = 0; + } + } + } + + if (feeds[i].id != priceIds[i]) + revert PythErrors.PriceFeedNotFoundWithinRange(); + } + } + + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { + return + parsePriceFeedUpdatesInternal( + updateData, + priceIds, + minPublishTime, + maxPublishTime, + false + ); + } + + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { + return + parsePriceFeedUpdatesInternal( + updateData, + priceIds, + minPublishTime, + maxPublishTime, + true + ); + } + + function createPriceFeedUpdateData( + bytes32 id, + int64 price, + uint64 conf, + int32 expo, + int64 emaPrice, + uint64 emaConf, + uint64 publishTime, + uint64 prevPublishTime + ) public pure returns (bytes memory priceFeedData) { + PythStructs.PriceFeed memory priceFeed; + + priceFeed.id = id; + + priceFeed.price.price = price; + priceFeed.price.conf = conf; + priceFeed.price.expo = expo; + priceFeed.price.publishTime = publishTime; + + priceFeed.emaPrice.price = emaPrice; + priceFeed.emaPrice.conf = emaConf; + priceFeed.emaPrice.expo = expo; + priceFeed.emaPrice.publishTime = publishTime; + + priceFeedData = abi.encode(priceFeed, prevPublishTime); + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol new file mode 100644 index 0000000000..1d8ba4509c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +import {PythStructs} from "./PythStructs.sol"; +import {IPyth} from "./IPyth.sol"; + +// This interface is forked from the Zerolend Adapter found here: +// https://github.com/zerolend/pyth-oracles/blob/master/contracts/PythAggregatorV3.sol +// Original license found under licenses/zerolend-pyth-oracles.md + +/** + * @title A port of the ChainlinkAggregatorV3 interface that supports Pyth price feeds + * @notice This does not store any roundId information on-chain. Please review the code before using this implementation. + * Users should deploy an instance of this contract to wrap every price feed id that they need to use. + */ +contract PythAggregatorV3 { + bytes32 public priceId; + IPyth public pyth; + + constructor(address _pyth, bytes32 _priceId) { + priceId = _priceId; + pyth = IPyth(_pyth); + } + + // Wrapper function to update the underlying Pyth price feeds. Not part of the AggregatorV3 interface but useful. + function updateFeeds(bytes[] calldata priceUpdateData) public payable { + // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data + // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. + // See section "How Pyth Works on EVM Chains" below for more information. + uint fee = pyth.getUpdateFee(priceUpdateData); + pyth.updatePriceFeeds{value: fee}(priceUpdateData); + + // refund remaining eth + payable(msg.sender).call{value: address(this).balance}(""); + } + + function decimals() public view virtual returns (uint8) { + PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); + return uint8(-1 * int8(price.expo)); + } + + function description() public pure returns (string memory) { + return "A port of a chainlink aggregator powered by pyth network feeds"; + } + + function version() public pure returns (uint256) { + return 1; + } + + function latestAnswer() public view virtual returns (int256) { + PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); + return int256(price.price); + } + + function latestTimestamp() public view returns (uint256) { + PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); + return price.publishTime; + } + + function latestRound() public view returns (uint256) { + // use timestamp as the round id + return latestTimestamp(); + } + + function getAnswer(uint256) public view returns (int256) { + return latestAnswer(); + } + + function getTimestamp(uint256) external view returns (uint256) { + return latestTimestamp(); + } + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); + return ( + _roundId, + int256(price.price), + price.publishTime, + price.publishTime, + _roundId + ); + } + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ) + { + PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); + roundId = uint80(price.publishTime); + return ( + roundId, + int256(price.price), + price.publishTime, + price.publishTime, + roundId + ); + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol new file mode 100644 index 0000000000..2b5457740e --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +library PythErrors { + // Function arguments are invalid (e.g., the arguments lengths mismatch) + // Signature: 0xa9cb9e0d + error InvalidArgument(); + // Update data is coming from an invalid data source. + // Signature: 0xe60dce71 + error InvalidUpdateDataSource(); + // Update data is invalid (e.g., deserialization error) + // Signature: 0xe69ffece + error InvalidUpdateData(); + // Insufficient fee is paid to the method. + // Signature: 0x025dbdd4 + error InsufficientFee(); + // There is no fresh update, whereas expected fresh updates. + // Signature: 0xde2c57fa + error NoFreshUpdate(); + // There is no price feed found within the given range or it does not exists. + // Signature: 0x45805f5d + error PriceFeedNotFoundWithinRange(); + // Price feed not found or it is not pushed on-chain yet. + // Signature: 0x14aebe68 + error PriceFeedNotFound(); + // Requested price is stale. + // Signature: 0x19abf40e + error StalePrice(); + // Given message is not a valid Wormhole VAA. + // Signature: 0x2acbe915 + error InvalidWormholeVaa(); + // Governance message is invalid (e.g., deserialization error). + // Signature: 0x97363b35 + error InvalidGovernanceMessage(); + // Governance message is not for this contract. + // Signature: 0x63daeb77 + error InvalidGovernanceTarget(); + // Governance message is coming from an invalid data source. + // Signature: 0x360f2d87 + error InvalidGovernanceDataSource(); + // Governance message is old. + // Signature: 0x88d1b847 + error OldGovernanceMessage(); + // The wormhole address to set in SetWormholeAddress governance is invalid. + // Signature: 0x13d3ed82 + error InvalidWormholeAddressToSet(); +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol new file mode 100644 index 0000000000..b3d2ee2c6a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +contract PythStructs { + // A price with a degree of uncertainty, represented as a price +- a confidence interval. + // + // The confidence interval roughly corresponds to the standard error of a normal distribution. + // Both the price and confidence are stored in a fixed-point numeric representation, + // `x * (10^expo)`, where `expo` is the exponent. + // + // Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how + // to how this price safely. + struct Price { + // Price + int64 price; + // Confidence interval around the price + uint64 conf; + // Price exponent + int32 expo; + // Unix timestamp describing when the price was published + uint publishTime; + } + + // PriceFeed represents a current aggregate price from pyth publisher feeds. + struct PriceFeed { + // The price ID. + bytes32 id; + // Latest available price + Price price; + // Latest available exponentially-weighted moving average price + Price emaPrice; + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol new file mode 100644 index 0000000000..04b7f51f34 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +library PythUtils { + /// @notice Converts a Pyth price to a uint256 with a target number of decimals + /// @param price The Pyth price + /// @param expo The Pyth price exponent + /// @param targetDecimals The target number of decimals + /// @return The price as a uint256 + /// @dev Function will lose precision if targetDecimals is less than the Pyth price decimals. + /// This method will truncate any digits that cannot be represented by the targetDecimals. + /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0 + function convertToUint( + int64 price, + int32 expo, + uint8 targetDecimals + ) public pure returns (uint256) { + if (price < 0 || expo > 0 || expo < -255) { + revert(); + } + + uint8 priceDecimals = uint8(uint32(-1 * expo)); + + if (targetDecimals >= priceDecimals) { + return + uint(uint64(price)) * + 10 ** uint32(targetDecimals - priceDecimals); + } else { + return + uint(uint64(price)) / + 10 ** uint32(priceDecimals - targetDecimals); + } + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md new file mode 100644 index 0000000000..dc50e8a482 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md @@ -0,0 +1,103 @@ +# Pyth Solidity SDK + +This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle using Solidity. Also, it contains [the Pyth Interface ABI](./abis/IPyth.json) that you can use in your libraries +to communicate with the Pyth contract. + +It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming Pyth data. + +## Installation + +###Truffle/Hardhat + +If you are using Truffle or Hardhat, simply install the NPM package: + +```bash +npm install @pythnetwork/pyth-sdk-solidity +``` + +###Foundry + +If you are using Foundry, you will need to create an NPM project if you don't already have one. +From the root directory of your project, run: + +```bash +npm init -y +npm install @pythnetwork/pyth-sdk-solidity +``` + +Then add the following line to your `remappings.txt` file: + +```text +@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity +``` + +## Example Usage + +To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this +interface in order to use the prices safely. + +For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed +you're interested in. The price feeds available on each chain are listed [below](#target-chains). + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; + +contract ExampleContract { + IPyth pyth; + + constructor(address pythContract) { + pyth = IPyth(pythContract); + } + + function getBtcUsdPrice( + bytes[] calldata priceUpdateData + ) public payable returns (PythStructs.Price memory) { + // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data + // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. + // See section "How Pyth Works on EVM Chains" below for more information. + uint fee = pyth.getUpdateFee(priceUpdateData); + pyth.updatePriceFeeds{ value: fee }(priceUpdateData); + + bytes32 priceID = 0xf9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b; + // Read the current value of priceID, aborting the transaction if the price has not been updated in the last 10 + // seconds. + return pyth.getPriceNoOlderThan(priceID, 10); + } +} + +``` + +## How Pyth Works on EVM Chains + +Pyth prices are published on Pythnet, and relayed to EVM chains using the [Wormhole Network](https://wormholenetwork.com/) as a cross-chain message passing bridge. The Wormhole Network observes when Pyth prices on Pythnet have changed and publishes an off-chain signed message attesting to this fact. This is explained in more detail [here](https://docs.wormholenetwork.com/wormhole/). + +This signed message can then be submitted to the Pyth contract on the EVM networks along the required update fee for it, which will verify the Wormhole message and update the Pyth contract with the new price. + +Please refer to [Pyth On-Demand Updates page](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand) for more information. + +## Solidity Target Chains + +[This](https://docs.pyth.network/documentation/pythnet-price-feeds/evm#networks) document contains list of the EVM networks that Pyth is available on. + +You can find a list of available price feeds [here](https://pyth.network/developers/price-feed-ids/). + +## Mocking Pyth + +[MockPyth](./MockPyth.sol) is a mock contract that you can use and deploy locally to mock Pyth contract behaviour. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds (the struct defined in [PythStructs](./PythStructs.sol)) as its argument. You can create encoded price feeds either by using web3.js or ethers ABI utilities or calling `createPriceFeedUpdateData` function in the mock contract. + +## Development + +### ABIs + +When making changes to a contract interface, please make sure to update the ABI files too. You can update it using `npm run generate-abi` and it will update the ABI files in [abis](./abis) directory. If you create a new contract, you also need to add the contract name in [the ABI generation script](./scripts/generateAbi.js#L5) so the script can create the ABI file for the new contract as well. + +### Releases + +We use [Semantic Versioning](https://semver.org/) for our releases. In order to release a new version of this package and publish it to npm, follow these steps: + +1. Run `npm version --no-git-tag-version`. This command will update the version of the package. Then push your changes to github. +2. Once your change is merged into `main`, create a release with tag `v` like `v1.5.2`, and a github action will automatically publish the new version of this package to npm. diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json new file mode 100644 index 0000000000..7b6724065f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json @@ -0,0 +1,661 @@ +[ + { + "inputs": [], + "name": "InvalidArgument", + "type": "error" + }, + { + "inputs": [], + "name": "NoFreshUpdate", + "type": "error" + }, + { + "inputs": [], + "name": "StalePrice", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "publishTime", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "conf", + "type": "uint64" + } + ], + "name": "PriceFeedUpdate", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getEmaPrice", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getEmaPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getEmaPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getPrice", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidTimePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "validTimePeriod", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdates", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "priceFeeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdatesUnique", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "priceFeeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "priceFeedExists", + "outputs": [ + { + "internalType": "bool", + "name": "exists", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "queryPriceFeed", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed", + "name": "priceFeed", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "updatePriceFeeds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64[]", + "name": "publishTimes", + "type": "uint64[]" + } + ], + "name": "updatePriceFeedsIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json new file mode 100644 index 0000000000..0b5fa851b2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json @@ -0,0 +1,452 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "publishTime", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "conf", + "type": "uint64" + } + ], + "name": "PriceFeedUpdate", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getEmaPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getEmaPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdates", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "priceFeeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdatesUnique", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "priceFeeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "updatePriceFeeds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64[]", + "name": "publishTimes", + "type": "uint64[]" + } + ], + "name": "updatePriceFeedsIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json new file mode 100644 index 0000000000..1a89732089 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json @@ -0,0 +1,33 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "publishTime", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "conf", + "type": "uint64" + } + ], + "name": "PriceFeedUpdate", + "type": "event" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json new file mode 100644 index 0000000000..a917b56a28 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json @@ -0,0 +1,746 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_validTimePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_singleUpdateFeeInWei", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InsufficientFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArgument", + "type": "error" + }, + { + "inputs": [], + "name": "NoFreshUpdate", + "type": "error" + }, + { + "inputs": [], + "name": "PriceFeedNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "PriceFeedNotFoundWithinRange", + "type": "error" + }, + { + "inputs": [], + "name": "StalePrice", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "publishTime", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "conf", + "type": "uint64" + } + ], + "name": "PriceFeedUpdate", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "int64", + "name": "emaPrice", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "emaConf", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "publishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "prevPublishTime", + "type": "uint64" + } + ], + "name": "createPriceFeedUpdateData", + "outputs": [ + { + "internalType": "bytes", + "name": "priceFeedData", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getEmaPrice", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getEmaPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getEmaPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getPrice", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getPriceNoOlderThan", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getPriceUnsafe", + "outputs": [ + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidTimePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdates", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "feeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxPublishTime", + "type": "uint64" + } + ], + "name": "parsePriceFeedUpdatesUnique", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "feeds", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "priceFeedExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "queryPriceFeed", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed", + "name": "priceFeed", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "updatePriceFeeds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64[]", + "name": "publishTimes", + "type": "uint64[]" + } + ], + "name": "updatePriceFeedsIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json new file mode 100644 index 0000000000..f8fdc192ce --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json @@ -0,0 +1,72 @@ +[ + { + "inputs": [], + "name": "InsufficientFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArgument", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGovernanceDataSource", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGovernanceMessage", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGovernanceTarget", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUpdateData", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUpdateDataSource", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidWormholeAddressToSet", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidWormholeVaa", + "type": "error" + }, + { + "inputs": [], + "name": "NoFreshUpdate", + "type": "error" + }, + { + "inputs": [], + "name": "OldGovernanceMessage", + "type": "error" + }, + { + "inputs": [], + "name": "PriceFeedNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "PriceFeedNotFoundWithinRange", + "type": "error" + }, + { + "inputs": [], + "name": "StalePrice", + "type": "error" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json new file mode 100644 index 0000000000..c30f138950 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json @@ -0,0 +1,31 @@ +[ + { + "inputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint8", + "name": "targetDecimals", + "type": "uint8" + } + ], + "name": "convertToUint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json new file mode 100644 index 0000000000..9622fc6320 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json @@ -0,0 +1,34 @@ +{ + "name": "@pythnetwork/pyth-sdk-solidity", + "version": "4.0.0", + "description": "Read prices from the Pyth oracle", + "repository": { + "type": "git", + "url": "https://github.com/pyth-network/pyth-crosschain", + "directory": "target_chains/ethereum/sdk/solidity" + }, + "scripts": { + "format": "prettier --write .", + "generate-abi": "generate-abis IPyth IPythEvents AbstractPyth MockPyth PythErrors PythUtils", + "check-abi": "git diff --exit-code abis", + "build": "solcjs --bin MockPyth.sol --base-path . -o build/" + }, + "keywords": [ + "pyth", + "solidity", + "oracle" + ], + "author": "Pyth Data Association", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/pyth-network/pyth-crosschain/issues" + }, + "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/sdk/solidity", + "devDependencies": { + "abi_generator": "0.0.0", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "^0.8.25" + }, + "gitHead": "a36f7ac5cc00a9c3c96beee509b58471ff5b9c40" +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol new file mode 100644 index 0000000000..ef5bed8460 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; + +contract MockPythSample is MockPyth { + uint randNonce = 0; + string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; + bytes32[] priceIds = new bytes32[](tickers.length); + + constructor( + uint validTimePeriod, uint singleUpdateFeeInWei + ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { + getPriceIds(); + for (uint i = 0; i< tickers.length; i++) { + uint randomNumber = randNumber(priceIds[i],10); + int64 price = int64(uint64(randomNumber + randNumber(priceIds[i],2))); + uint64 conf = uint64(randomNumber+ randNumber(priceIds[i],3)); + int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40))); + int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5))); + uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6)); + createPriceFeedUpdateData(priceIds[i],price, conf, expo,emaPrice, emaConf, uint64(block.timestamp),0); + } + } + + function getPriceIds() internal { + for (uint i = 0; i < tickers.length; i++) { + bytes memory tempEmptyStringTest = bytes(tickers[i]); + if (tempEmptyStringTest.length == 0) { + priceIds[i] = bytes32(0); + } + else { + priceIds[i] = keccak256(abi.encodePacked(tickers[i])); + } + } + } + + function randNumber(bytes32 ticker, uint salt) internal returns(uint) + { + // increase nonce + randNonce++; + return uint(keccak256(abi.encodePacked(block.timestamp, ticker,msg.sender,randNonce, salt))) % randNonce; + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs new file mode 100644 index 0000000000..bf06524e96 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -0,0 +1,24 @@ +#![cfg_attr(not(test), no_std, no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use stylus_sdk::{ prelude::{entrypoint,public, sol_storage}, storage::StorageAddress, alloy_primitives::{FixedBytes, Uint}}; +use pyth_stylus::pyth::functions::get_price_no_older_than; + +sol_storage! { + #[entrypoint] + struct FunctionCallsExample { + address pyth_address; + } +} + + +#[public] +impl FunctionCallsExample { + pub fn check_price(&mut self) -> Result<(), Vec> { + let price = get_price_no_older_than(self, self.pyth_address,FixedBytes::from("BTC"),Uint::from(10000000000))?; + Ok(()) + } + +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs deleted file mode 100644 index e7a11a969c..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs new file mode 100644 index 0000000000..34ec44e34a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs @@ -0,0 +1,64 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Pyth { + function approve(address to, uint256 tokenId) external; + #[derive(Debug)] + function balanceOf(address owner) external view returns (uint256 balance); + #[derive(Debug)] + function getApproved(uint256 tokenId) external view returns (address approved); + #[derive(Debug)] + function isApprovedForAll(address owner, address operator) external view returns (bool approved); + #[derive(Debug)] + function ownerOf(uint256 tokenId) external view returns (address ownerOf); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + function setApprovalForAll(address operator, bool approved) external; + function totalSupply() external view returns (uint256 totalSupply); + function transferFrom(address from, address to, uint256 tokenId) external; + + function mint(address to, uint256 tokenId) external; + function burn(uint256 tokenId) external; + + function paused() external view returns (bool paused); + function pause() external; + function unpause() external; + #[derive(Debug)] + function whenPaused() external view; + #[derive(Debug)] + function whenNotPaused() external view; + + #[derive(Debug)] + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + #[derive(Debug)] + function tokenByIndex(uint256 index) external view returns (uint256 tokenId); + + function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); + + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + error ERC721InsufficientApproval(address operator, uint256 tokenId); + error ERC721InvalidApprover(address approver); + error ERC721InvalidOperator(address operator); + error ERC721InvalidOwner(address owner); + error ERC721InvalidReceiver(address receiver); + error ERC721InvalidSender(address sender); + error ERC721NonexistentToken(uint256 tokenId); + error ERC721OutOfBoundsIndex(address owner, uint256 index); + error ERC721EnumerableForbiddenBatchMint(); + error EnforcedPause(); + error ExpectedPause(); + + #[derive(Debug, PartialEq)] + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + #[derive(Debug, PartialEq)] + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + #[derive(Debug, PartialEq)] + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + #[derive(Debug, PartialEq)] + event Paused(address account); + #[derive(Debug, PartialEq)] + event Unpaused(address account); + } +); \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs new file mode 100644 index 0000000000..cd4edfde11 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs @@ -0,0 +1,25 @@ +#![cfg(feature = "e2e")] + +use std::print; + +use abi::Pyth; +use alloy::primitives::{fixed_bytes, uint, Address, Bytes, U256}; +use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; + +fn random_token_id() -> U256 { + let num: u32 = rand::random(); + U256::from(num) +} + +#[e2e::test] +async fn constructs(alice: Account) -> eyre::Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; + print_ln!("Contract address: {}", contract_addr); + // let contract = Erc721::new(contract_addr, &alice.wallet); + + // let Erc721::pausedReturn { paused } = contract.paused().call().await?; + + // assert_eq!(false, paused); + + Ok(()) +} \ No newline at end of file From 05b9593d5d38752a40fab5e1776914db8640cfbe Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 23 Oct 2024 17:25:36 +0000 Subject: [PATCH 033/160] chore: Changed motsu --- target_chains/ethereum/sdk/stylus/Cargo.lock | 8 +- target_chains/ethereum/sdk/stylus/Cargo.toml | 10 +- .../sdk/stylus/lib/motsu-proc/Cargo.toml | 26 ++ .../sdk/stylus/lib/motsu-proc/README.md | 5 + .../sdk/stylus/lib/motsu-proc/src/lib.rs | 57 +++ .../sdk/stylus/lib/motsu-proc/src/test.rs | 52 +++ .../ethereum/sdk/stylus/lib/motsu/Cargo.toml | 19 + .../ethereum/sdk/stylus/lib/motsu/README.md | 62 +++ .../sdk/stylus/lib/motsu/src/context.rs | 45 +++ .../ethereum/sdk/stylus/lib/motsu/src/lib.rs | 61 +++ .../sdk/stylus/lib/motsu/src/prelude.rs | 6 + .../sdk/stylus/lib/motsu/src/shims.rs | 378 ++++++++++++++++++ .../sdk/stylus/lib/motsu/src/storage.rs | 32 ++ 13 files changed, 750 insertions(+), 11 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/README.md create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs create mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 6dfb40f852..158a4a729e 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -294,9 +294,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" +checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 863100a51e..f958027d99 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -66,14 +66,13 @@ syn = { version = "2.0.58", features = ["full"] } proc-macro2 = "1.0.79" quote = "1.0.35" -# motsu test -motsu = "=0.1.0-rc" # members pyth-stylus = { path = "contracts" } e2e = { path = "lib/e2e" } e2e-proc = {path = "lib/e2e-proc"} - +motsu = { path = "lib/motsu"} +motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } [profile.release] codegen-units = 1 @@ -87,7 +86,4 @@ debug-assertions = false incremental = false [profile.dev] -panic = "abort" - - - +panic = "abort" \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml new file mode 100644 index 0000000000..99cbdb6d38 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "motsu-proc" +description = "Mostu's Procedural Macros" +edition.workspace = true +categories = ["development-tools::testing", "cryptography::cryptocurrencies"] +keywords = ["arbitrum", "ethereum", "stylus", "unit-tests", "tests"] +license.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true + +[dev-dependencies] +motsu.workspace = true +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true + +[lib] +proc-macro = true + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md new file mode 100644 index 0000000000..40d2ef4b13 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md @@ -0,0 +1,5 @@ +# Motsu's Procedural Macros + +This crate contains procedural macros used in [`motsu`]. + +[motsu]: ../motsu/README.md diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs new file mode 100644 index 0000000000..c58093e86a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs @@ -0,0 +1,57 @@ +//! Procedural macro definitions used in `motsu`. +use proc_macro::TokenStream; + +/// Shorthand to print nice errors. +/// +/// Note that it's defined before the module declarations. +macro_rules! error { + ($tokens:expr, $($msg:expr),+ $(,)?) => {{ + let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); + return error.to_compile_error().into(); + }}; + (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ + return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) + }}; +} + +mod test; + +/// Defines a unit test that provides access to Stylus' execution context. +/// +/// Internally, this is a thin wrapper over `#[test]` that gives access to +/// affordances like contract storage and `msg::sender`. If you don't need +/// them, you can pass no arguments to the test function or simply use +/// `#[test]` instead of `#[motsu::test]`. +/// +/// # Examples +/// +/// ```rust,ignore +/// #[cfg(test)] +/// mod tests { +/// #[motsu::test] +/// fn reads_balance(contract: Erc20) { +/// let balance = contract.balance_of(Address::ZERO); +/// assert_eq!(U256::ZERO, balance); +/// +/// let owner = msg::sender(); +/// let one = U256::from(1); +/// contract._balances.setter(owner).set(one); +/// let balance = contract.balance_of(owner); +/// assert_eq!(one, balance); +/// } +/// } +/// ``` +/// +/// ```rust,ignore +/// #[cfg(test)] +/// mod tests { +/// #[motsu::test] +/// fn t() { // If no params, it expands to a `#[test]`. +/// ... +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream { + test::test(&attr, input) +} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs new file mode 100644 index 0000000000..fe4af7d000 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs @@ -0,0 +1,52 @@ +//! Defines the `#[motsu::test]` procedural macro. +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, FnArg}; + +/// Defines a unit test that provides access to Stylus' execution context. +/// +/// For more information see [`crate::test`]. +pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { + let item_fn = parse_macro_input!(input as syn::ItemFn); + let attrs = &item_fn.attrs; + let sig = &item_fn.sig; + let fn_name = &sig.ident; + let fn_return_type = &sig.output; + let fn_block = &item_fn.block; + let fn_args = &sig.inputs; + + // If the test function has no params, then it doesn't need access to the + // contract, so it is just a regular test. + if fn_args.is_empty() { + return quote! { + #( #attrs )* + #[test] + fn #fn_name() #fn_return_type { + let _lock = ::motsu::prelude::acquire_storage(); + let res = #fn_block; + ::motsu::prelude::reset_storage(); + res + } + } + .into(); + } + + // We can unwrap because we handle the empty case above. We don't support + // more than one parameter for now, so we skip them. + let arg = fn_args.first().unwrap(); + let FnArg::Typed(arg) = arg else { + error!(arg, "unexpected receiver argument in test signature"); + }; + let contract_arg_binding = &arg.pat; + let contract_ty = &arg.ty; + quote! { + #( #attrs )* + #[test] + fn #fn_name() #fn_return_type { + ::motsu::prelude::with_context::<#contract_ty>(| #contract_arg_binding | + #fn_block + ) + } + } + .into() +} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml new file mode 100644 index 0000000000..5f3e5958ac --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "motsu" +description = "Unit Testing for Stylus" +edition.workspace = true +categories = ["development-tools::testing", "cryptography::cryptocurrencies"] +keywords = ["arbitrum", "ethereum", "stylus", "unit-tests", "tests"] +license.workspace = true +repository.workspace = true +version = "0.1.0" + +[dependencies] +const-hex.workspace = true +once_cell.workspace = true +tiny-keccak.workspace = true +stylus-sdk.workspace = true +motsu-proc.workspace = true + +[lints] +workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/README.md b/target_chains/ethereum/sdk/stylus/lib/motsu/README.md new file mode 100644 index 0000000000..f7e8c074c0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/README.md @@ -0,0 +1,62 @@ +# Motsu (持つ) - Unit Testing for Stylus + +This crate enables unit-testing for Stylus contracts. It abstracts away the +machinery necessary for writing tests behind a `#[motsu::test]` procedural +macro. + +`motsu` means ["to hold"](https://jisho.org/word/%E6%8C%81%E3%81%A4) in +Japanese -- we hold a stylus in our hand. + +## Usage + +Annotate tests with `#[motsu::test]` instead of `#[test]` to get access to VM +affordances. + +Note that we require contracts to implement `stylus_sdk::prelude::StorageType`. +This trait is typically implemented by default with `stylus_proc::sol_storage` macro. + +```rust +#[cfg(test)] +mod tests { + use contracts::token::erc20::Erc20; + + #[motsu::test] + fn reads_balance(contract: Erc20) { + let balance = contract.balance_of(Address::ZERO); // Access storage. + assert_eq!(balance, U256::ZERO); + } +} +``` + +Annotating a test function that accepts no parameters will make `#[motsu::test]` +behave the same as `#[test]`. + +```rust,ignore +#[cfg(test)] +mod tests { + #[motsu::test] + fn t() { // If no params, it expands to a `#[test]`. + // ... + } +} +``` + +Note that currently, test suites using `motsu::test` will run serially because +of global access to storage. + +### Notice + +We maintain this crate on a best-effort basis. We use it extensively on our own +tests, so we will add here any symbols we may need. However, since we expect +this to be a temporary solution, don't expect us to address all requests. + +That being said, please do open an issue to start a discussion, keeping in mind +our [code of conduct] and [contribution guidelines]. + +[code of conduct]: ../../CODE_OF_CONDUCT.md + +[contribution guidelines]: ../../CONTRIBUTING.md + +## Security + +Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs new file mode 100644 index 0000000000..265790f140 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs @@ -0,0 +1,45 @@ +//! Unit-testing context for Stylus contracts. +use std::sync::{Mutex, MutexGuard}; + +use stylus_sdk::{alloy_primitives::uint, prelude::StorageType}; + +use crate::storage::reset_storage; + +/// A global static mutex. +/// +/// We use this for scenarios where concurrent mutation of storage is wanted. +/// For example, when a test harness is running, this ensures each test +/// accesses storage in an non-overlapping manner. +/// +/// See [`with_context`]. +pub(crate) static STORAGE_MUTEX: Mutex<()> = Mutex::new(()); + +/// Acquires access to storage. +pub fn acquire_storage() -> MutexGuard<'static, ()> { + STORAGE_MUTEX.lock().unwrap_or_else(|e| { + reset_storage(); + e.into_inner() + }) +} + +/// Decorates a closure by running it with exclusive access to storage. +#[allow(clippy::module_name_repetitions)] +pub fn with_context(closure: impl FnOnce(&mut C)) { + let _lock = acquire_storage(); + let mut contract = C::default(); + closure(&mut contract); + reset_storage(); +} + +/// Initializes fields of contract storage and child contract storages with +/// default values. +pub trait DefaultStorage: StorageType { + /// Initializes fields of contract storage and child contract storages with + /// default values. + #[must_use] + fn default() -> Self { + unsafe { Self::new(uint!(0_U256), 0) } + } +} + +impl DefaultStorage for ST {} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs new file mode 100644 index 0000000000..98fcd5dbd0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs @@ -0,0 +1,61 @@ +//! # Motsu - Unit Testing for Stylus +//! +//! This crate enables unit-testing for Stylus contracts. It abstracts away the +//! machinery necessary for writing tests behind a +//! [`#[motsu::test]`][test_attribute] procedural macro. +//! +//! The name `motsu` is an analogy to the place where you put your fingers to +//! hold a stylus pen. +//! +//! ## Usage +//! +//! Annotate tests with [`#[motsu::test]`][test_attribute] instead of `#[test]` +//! to get access to VM affordances. +//! +//! Note that we require contracts to implement +//! `stylus_sdk::prelude::StorageType`. This trait is typically implemented by +//! default with `stylus_proc::sol_storage` macro. +//! +//! ```rust +//! #[cfg(test)] +//! mod tests { +//! use contracts::token::erc20::Erc20; +//! +//! #[motsu::test] +//! fn reads_balance(contract: Erc20) { +//! let balance = contract.balance_of(Address::ZERO); // Access storage. +//! assert_eq!(balance, U256::ZERO); +//! } +//! } +//! ``` +//! +//! Annotating a test function that accepts no parameters will make +//! [`#[motsu::test]`][test_attribute] behave the same as `#[test]`. +//! +//! ```rust,ignore +//! #[cfg(test)] +//! mod tests { +//! #[motsu::test] // Equivalent to #[test] +//! fn test_fn() { +//! ... +//! } +//! } +//! ``` +//! +//! Note that currently, test suites using [`motsu::test`][test_attribute] will +//! run serially because of global access to storage. +//! +//! ### Notice +//! +//! We maintain this crate on a best-effort basis. We use it extensively on our +//! own tests, so we will add here any symbols we may need. However, since we +//! expect this to be a temporary solution, don't expect us to address all +//! requests. +//! +//! [test_attribute]: crate::test +mod context; +pub mod prelude; +mod shims; +mod storage; + +pub use motsu_proc::test; diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs new file mode 100644 index 0000000000..b4b541a8f8 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs @@ -0,0 +1,6 @@ +//! Common imports for `motsu` tests. +pub use crate::{ + context::{acquire_storage, with_context, DefaultStorage}, + shims::*, + storage::reset_storage, +}; diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs new file mode 100644 index 0000000000..0860a61423 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs @@ -0,0 +1,378 @@ +//! Shims that mock common host imports in Stylus `wasm` programs. +//! +//! Most of the documentation is taken from the [Stylus source]. +//! +//! We allow unsafe here because safety is guaranteed by the Stylus team. +//! +//! [Stylus source]: https://github.com/OffchainLabs/stylus/blob/484efac4f56fb70f96d4890748b8ec2543d88acd/arbitrator/wasm-libraries/user-host-trait/src/lib.rs +//! +//! ## Motivation +//! +//! Without these shims we can't currently run unit tests for stylus contracts, +//! since the symbols the compiled binaries expect to find are not there. +//! +//! If you run `cargo test` on a fresh Stylus project, it will error with: +//! +//! ```terminal +//! dyld[97792]: missing symbol called +//! ``` +//! +//! This crate is a temporary solution until the Stylus team provides us with a +//! different and more stable mechanism for unit-testing our contracts. +//! +//! ## Usage +//! +//! Import these shims in your test modules as `motsu::prelude::*` to populate +//! the namespace with the appropriate symbols. +//! +//! ```rust,ignore +//! #[cfg(test)] +//! mod tests { +//! use contracts::token::erc20::Erc20; +//! +//! #[motsu::test] +//! fn reads_balance(contract: Erc20) { +//! let balance = contract.balance_of(Address::ZERO); // Access storage. +//! assert_eq!(balance, U256::ZERO); +//! } +//! } +//! ``` +//! +//! Note that for proper usage, tests should have exclusive access to storage, +//! since they run in parallel, which may cause undesired results. +//! +//! One solution is to wrap tests with a function that acquires a global mutex: +//! +//! ```rust,no_run +//! use std::sync::{Mutex, MutexGuard}; +//! +//! use motsu::prelude::reset_storage; +//! +//! pub static STORAGE_MUTEX: Mutex<()> = Mutex::new(()); +//! +//! pub fn acquire_storage() -> MutexGuard<'static, ()> { +//! STORAGE_MUTEX.lock().unwrap() +//! } +//! +//! pub fn with_context(closure: impl FnOnce(&mut C)) { +//! let _lock = acquire_storage(); +//! let mut contract = C::default(); +//! closure(&mut contract); +//! reset_storage(); +//! } +//! +//! #[motsu::test] +//! fn reads_balance() { +//! let balance = token.balance_of(Address::ZERO); +//! assert_eq!(balance, U256::ZERO); +//! } +//! ``` +#![allow(clippy::missing_safety_doc)] +use std::slice; + +use tiny_keccak::{Hasher, Keccak}; + +use crate::storage::{read_bytes32, write_bytes32, STORAGE}; + +pub(crate) const WORD_BYTES: usize = 32; +pub(crate) type Bytes32 = [u8; WORD_BYTES]; + +/// Efficiently computes the [`keccak256`] hash of the given preimage. +/// The semantics are equivalent to that of the EVM's [`SHA3`] opcode. +/// +/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 +/// [`SHA3`]: https://www.evm.codes/#20 +#[no_mangle] +pub unsafe extern "C" fn native_keccak256( + bytes: *const u8, + len: usize, + output: *mut u8, +) { + let mut hasher = Keccak::v256(); + + let data = unsafe { slice::from_raw_parts(bytes, len) }; + hasher.update(data); + + let output = unsafe { slice::from_raw_parts_mut(output, WORD_BYTES) }; + hasher.finalize(output); +} + +/// Reads a 32-byte value from permanent storage. Stylus's storage format is +/// identical to that of the EVM. This means that, under the hood, this hostio +/// is accessing the 32-byte value stored in the EVM state trie at offset +/// `key`, which will be `0` when not previously set. The semantics, then, are +/// equivalent to that of the EVM's [`SLOAD`] opcode. +/// +/// [`SLOAD`]: https://www.evm.codes/#54 +/// +/// # Panics +/// +/// May panic if unable to lock `STORAGE`. +#[no_mangle] +pub unsafe extern "C" fn storage_load_bytes32(key: *const u8, out: *mut u8) { + let key = unsafe { read_bytes32(key) }; + + let value = STORAGE + .lock() + .unwrap() + .get(&key) + .map(Bytes32::to_owned) + .unwrap_or_default(); + + unsafe { write_bytes32(out, value) }; +} + +/// Writes a 32-byte value to the permanent storage cache. Stylus's storage +/// format is identical to that of the EVM. This means that, under the hood, +/// this hostio represents storing a 32-byte value into the EVM state trie at +/// offset `key`. Refunds are tabulated exactly as in the EVM. The semantics, +/// then, are equivalent to that of the EVM's [`SSTORE`] opcode. +/// +/// Note: because the value is cached, one must call `storage_flush_cache` to +/// persist it. +/// +/// [`SSTORE`]: https://www.evm.codes/#55 +/// +/// # Panics +/// +/// May panic if unable to lock `STORAGE`. +#[no_mangle] +pub unsafe extern "C" fn storage_cache_bytes32( + key: *const u8, + value: *const u8, +) { + let (key, value) = unsafe { (read_bytes32(key), read_bytes32(value)) }; + STORAGE.lock().unwrap().insert(key, value); +} + +/// Persists any dirty values in the storage cache to the EVM state trie, +/// dropping the cache entirely if requested. Analogous to repeated invocations +/// of [`SSTORE`]. +/// +/// [`SSTORE`]: https://www.evm.codes/#55 +pub fn storage_flush_cache(_: bool) { + // No-op: we don't use the cache in our unit-tests. +} + +/// Dummy msg sender set for tests. +pub const MSG_SENDER: &[u8; 42] = b"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"; + +/// Dummy contract address set for tests. +pub const CONTRACT_ADDRESS: &[u8; 42] = + b"0xdCE82b5f92C98F27F116F70491a487EFFDb6a2a9"; + +/// Arbitrum's CHAID ID. +pub const CHAIN_ID: u64 = 42161; + +/// Externally Owned Account (EOA) code hash. +pub const EOA_CODEHASH: &[u8; 66] = + b"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; + +/// Gets the address of the account that called the program. For normal +/// L2-to-L2 transactions the semantics are equivalent to that of the EVM's +/// [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. +/// +/// For L1-to-L2 retryable ticket transactions, the top-level sender's address +/// will be aliased. See [`Retryable Ticket Address Aliasing`][aliasing] for +/// more information on how this works. +/// +/// [`CALLER`]: https://www.evm.codes/#33 +/// [`DELEGATE_CALL`]: https://www.evm.codes/#f4 +/// [aliasing]: https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing +/// +/// # Panics +/// +/// May panic if fails to parse `MSG_SENDER` as an address. +#[no_mangle] +pub unsafe extern "C" fn msg_sender(sender: *mut u8) { + let addr = const_hex::const_decode_to_array::<20>(MSG_SENDER).unwrap(); + std::ptr::copy(addr.as_ptr(), sender, 20); +} + +/// Gets the address of the current program. The semantics are equivalent to +/// that of the EVM's [`ADDRESS`] opcode. +/// +/// [`ADDRESS`]: https://www.evm.codes/#30 +/// +/// # Panics +/// +/// May panic if fails to parse `CONTRACT_ADDRESS` as an address. +#[no_mangle] +pub unsafe extern "C" fn contract_address(address: *mut u8) { + let addr = + const_hex::const_decode_to_array::<20>(CONTRACT_ADDRESS).unwrap(); + std::ptr::copy(addr.as_ptr(), address, 20); +} + +/// Gets the chain ID of the current chain. The semantics are equivalent to +/// that of the EVM's [`CHAINID`] opcode. +/// +/// [`CHAINID`]: https://www.evm.codes/#46 +#[no_mangle] +pub unsafe extern "C" fn chainid() -> u64 { + CHAIN_ID +} + +/// Emits an EVM log with the given number of topics and data, the first bytes +/// of which should be the 32-byte-aligned topic data. The semantics are +/// equivalent to that of the EVM's [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and +/// [`LOG4`] opcodes based on the number of topics specified. Requesting more +/// than `4` topics will induce a revert. +/// +/// [`LOG0`]: https://www.evm.codes/#a0 +/// [`LOG1`]: https://www.evm.codes/#a1 +/// [`LOG2`]: https://www.evm.codes/#a2 +/// [`LOG3`]: https://www.evm.codes/#a3 +/// [`LOG4`]: https://www.evm.codes/#a4 +#[no_mangle] +pub unsafe extern "C" fn emit_log(_: *const u8, _: usize, _: usize) { + // No-op: we don't check for events in our unit-tests. +} + +/// Gets the code hash of the account at the given address. +/// The semantics are equivalent to that of the EVM's [`EXT_CODEHASH`] opcode. +/// Note that the code hash of an account without code will be the empty hash +/// `keccak("") = +/// c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. +/// +/// [`EXT_CODEHASH`]: https://www.evm.codes/#3F +/// +/// # Panics +/// +/// May panic if fails to parse `ACCOUNT_CODEHASH` as a keccack hash. +#[no_mangle] +pub unsafe extern "C" fn account_codehash(_address: *const u8, dest: *mut u8) { + let account_codehash = + const_hex::const_decode_to_array::<32>(EOA_CODEHASH).unwrap(); + + std::ptr::copy(account_codehash.as_ptr(), dest, 32); +} + +/// Returns the length of the last EVM call or deployment return result, or `0` +/// if neither have happened during the program's execution. The semantics are +/// equivalent to that of the EVM's [`RETURN_DATA_SIZE`] opcode. +/// +/// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d +#[no_mangle] +pub unsafe extern "C" fn return_data_size() -> usize { + // TODO: #156 + // No-op: we do not use this function in our unit-tests, + // but the binary does include it. + 0 +} + +/// Copies the bytes of the last EVM call or deployment return result. Does not +/// revert if out of bounds, but rather copies the overlapping portion. The +/// semantics are otherwise equivalent to that of the EVM's [`RETURN_DATA_COPY`] +/// opcode. +/// +/// Returns the number of bytes written. +/// +/// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e +#[no_mangle] +pub unsafe extern "C" fn read_return_data( + _dest: *mut u8, + _offset: usize, + _size: usize, +) -> usize { + // TODO: #156 + // No-op: we do not use this function in our unit-tests, + // but the binary does include it. + 0 +} + +/// Calls the contract at the given address with options for passing value and +/// to limit the amount of gas supplied. The return status indicates whether the +/// call succeeded, and is nonzero on failure. +/// +/// In both cases `return_data_len` will store the length of the result, the +/// bytes of which can be read via the `read_return_data` hostio. The bytes are +/// not returned directly so that the programmer can potentially save gas by +/// choosing which subset of the return result they'd like to copy. +/// +/// The semantics are equivalent to that of the EVM's [`CALL`] opcode, including +/// callvalue stipends and the 63/64 gas rule. This means that supplying the +/// `u64::MAX` gas can be used to send as much as possible. +/// +/// [`CALL`]: https://www.evm.codes/#f1 +#[no_mangle] +pub unsafe extern "C" fn call_contract( + _contract: *const u8, + _calldata: *const u8, + _calldata_len: usize, + _value: *const u8, + _gas: u64, + _return_data_len: *mut usize, +) -> u8 { + // TODO: #156 + // No-op: we do not use this function in our unit-tests, + // but the binary does include it. + 0 +} + +/// Static calls the contract at the given address, with the option to limit the +/// amount of gas supplied. The return status indicates whether the call +/// succeeded, and is nonzero on failure. +/// +/// In both cases `return_data_len` will store the length of the result, the +/// bytes of which can be read via the `read_return_data` hostio. The bytes are +/// not returned directly so that the programmer can potentially save gas by +/// choosing which subset of the return result they'd like to copy. +/// +/// The semantics are equivalent to that of the EVM's [`STATIC_CALL`] opcode, +/// including the 63/64 gas rule. This means that supplying `u64::MAX` gas can +/// be used to send as much as possible. +/// +/// [`STATIC_CALL`]: https://www.evm.codes/#FA +#[no_mangle] +pub unsafe extern "C" fn static_call_contract( + _contract: *const u8, + _calldata: *const u8, + _calldata_len: usize, + _gas: u64, + _return_data_len: *mut usize, +) -> u8 { + // TODO: #156 + // No-op: we do not use this function in our unit-tests, + // but the binary does include it. + 0 +} + +/// Delegate calls the contract at the given address, with the option to limit +/// the amount of gas supplied. The return status indicates whether the call +/// succeeded, and is nonzero on failure. +/// +/// In both cases `return_data_len` will store the length of the result, the +/// bytes of which can be read via the `read_return_data` hostio. The bytes are +/// not returned directly so that the programmer can potentially save gas by +/// choosing which subset of the return result they'd like to copy. +/// +/// The semantics are equivalent to that of the EVM's [`DELEGATE_CALL`] opcode, +/// including the 63/64 gas rule. This means that supplying `u64::MAX` gas can +/// be used to send as much as possible. +/// +/// [`DELEGATE_CALL`]: https://www.evm.codes/#F4 +#[no_mangle] +pub unsafe extern "C" fn delegate_call_contract( + _contract: *const u8, + _calldata: *const u8, + _calldata_len: usize, + _gas: u64, + _return_data_len: *mut usize, +) -> u8 { + // TODO: #156 + // No-op: we do not use this function in our unit-tests, + // but the binary does include it. + 0 +} + +/// Gets a bounded estimate of the Unix timestamp at which the Sequencer +/// sequenced the transaction. See [`Block Numbers and Time`] for more +/// information on how this value is determined. +/// +/// [`Block Numbers and Time`]: https://developer.arbitrum.io/time +#[no_mangle] +pub unsafe extern "C" fn block_timestamp() -> u64 { + // Epoch timestamp: 1st January 2025 00::00::00 + 1_735_689_600 +} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs new file mode 100644 index 0000000000..4e8d23d4aa --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs @@ -0,0 +1,32 @@ +//! Shims for storage operations. +use std::{collections::HashMap, ptr, sync::Mutex}; + +use once_cell::sync::Lazy; + +use crate::shims::{Bytes32, WORD_BYTES}; + +/// Storage mock: A global mutable key-value store. +pub(crate) static STORAGE: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +/// Read the word at address `key`. +pub(crate) unsafe fn read_bytes32(key: *const u8) -> Bytes32 { + let mut res = Bytes32::default(); + ptr::copy(key, res.as_mut_ptr(), WORD_BYTES); + res +} + +/// Write the word `val` to the location pointed by `key`. +pub(crate) unsafe fn write_bytes32(key: *mut u8, val: Bytes32) { + ptr::copy(val.as_ptr(), key, WORD_BYTES); +} + +/// Clears storage, removing all key-value pairs. +/// +/// # Panics +/// +/// May panic if the storage lock is already held by the current thread. +#[allow(clippy::module_name_repetitions)] +pub fn reset_storage() { + STORAGE.lock().unwrap().clear(); +} From 70e2e6cff84b8faff542e668bcd309bf30adaf61 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 15:18:58 +0000 Subject: [PATCH 034/160] chore: Created Proxy call example --- .../external-call-contracts/Cargo.toml | 22 ------------------- .../external-call-contracts/src/lib.rs | 0 2 files changed, 22 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml deleted file mode 100644 index 725b9f9cd4..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "external-call-contracts" -authors.workspace = true -license.workspace = true -keywords.workspace = true -repository.workspace = true -publish = false -version = "0.0.0" - -[dependencies] -alloy-primitives.workspace = true -stylus-sdk.workspace = true -mini-alloc.workspace = true - -[dev-dependencies] -alloy.workspace = true -eyre.workspace = true -tokio.workspace = true -e2e.workspace = true - -[features] -e2e = [] diff --git a/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/external-call-contracts/src/lib.rs deleted file mode 100644 index e69de29bb2..0000000000 From 5ad08c473cc00025d6c792ea4e9870e1e87f22f4 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 15:27:14 +0000 Subject: [PATCH 035/160] feture: Example for both type of call --- target_chains/ethereum/sdk/stylus/Cargo.lock | 176 +++++++----------- target_chains/ethereum/sdk/stylus/Cargo.toml | 12 +- .../stylus/examples/function-calls/Cargo.toml | 1 + .../stylus/examples/function-calls/src/lib.rs | 7 +- .../examples/proxy-call-contracts/Cargo.toml | 24 +++ .../examples/proxy-call-contracts/src/lib.rs | 28 +++ 6 files changed, 128 insertions(+), 120 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 158a4a729e..9e6d1d7824 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -78,11 +78,10 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.40" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4932d790c723181807738cf1ac68198ab581cd699545b155601332541ee47bd" +checksum = "1752d7d62e2665da650a36d84abbf239f812534475d51f072a49a533513b7cdd" dependencies = [ - "alloy-primitives 0.8.9", "num_enum", "strum", ] @@ -94,7 +93,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da374e868f54c7f4ad2ad56829827badca388efd645f8cf5fccc61c2b5343504" dependencies = [ "alloy-eips", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-rlp", "alloy-serde", "c-kzg", @@ -110,7 +109,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-network", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", "alloy-sol-types", @@ -128,7 +127,7 @@ checksum = "5af3faff14c12c8b11037e0a093dd157c3702becb8435577a2408534d0758315" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-types", ] @@ -139,7 +138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", "const-hex", @@ -155,7 +154,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f76ecab54890cdea1e4808fc0891c7e6cfcf71fe1a9fe26810c7280ef768f4ed" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-rlp", "alloy-serde", "c-kzg", @@ -170,7 +169,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca15afde1b6d15e3fc1c97421262b1bbb37aee45752e3c8b6d6f13f776554ff" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-serde", "serde", ] @@ -181,7 +180,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-type-parser", "serde", "serde_json", @@ -193,7 +192,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d6f34930b7e3e2744bcc79056c217f00cb2abb33bc5d4ff88da7623c5bb078b" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "serde", "serde_json", "thiserror", @@ -209,7 +208,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -230,7 +229,7 @@ dependencies = [ "bytes", "cfg-if 1.0.0", "const-hex", - "derive_more 0.99.18", + "derive_more", "getrandom", "hex-literal", "itoa", @@ -243,23 +242,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "alloy-primitives" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71738eb20c42c5fb149571e76536a0f309d142f3957c28791662b96baf77a3d" -dependencies = [ - "bytes", - "cfg-if 1.0.0", - "const-hex", - "derive_more 1.0.0", - "hex-literal", - "itoa", - "paste", - "ruint", - "tiny-keccak", -] - [[package]] name = "alloy-provider" version = "0.1.4" @@ -271,7 +253,7 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-eth", "alloy-transport", @@ -311,7 +293,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -353,7 +335,7 @@ checksum = "ab4123ee21f99ba4bd31bfa36ba89112a18a500f8b452f02b35708b1b951e2b9" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-sol-types", @@ -369,7 +351,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9416c52959e66ead795a11f4a86c248410e9e368a0765710e57055b8a1774dd6" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "serde", "serde_json", ] @@ -380,7 +362,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b33753c09fa1ad85e5b092b8dc2372f1e337a42e84b9b4cff9fede75ba4adb32" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "async-trait", "auto_impl", "elliptic-curve", @@ -396,7 +378,7 @@ checksum = "6dfc9c26fe6c6f1bad818c9a976de9044dd12e1f75f1f156a801ee3e8148c1b6" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-signer", "async-trait", "elliptic-curve", @@ -417,7 +399,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -434,7 +416,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", "syn-solidity", "tiny-keccak", ] @@ -452,7 +434,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.82", + "syn 2.0.83", "syn-solidity", ] @@ -472,7 +454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-macro", "const-hex", "serde", @@ -710,7 +692,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -721,7 +703,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -732,7 +714,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -986,7 +968,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -1238,7 +1220,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -1249,7 +1231,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -1296,28 +1278,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.82", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.82", - "unicode-xid", + "syn 2.0.83", ] [[package]] @@ -1367,7 +1328,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -1447,7 +1408,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -1488,19 +1449,6 @@ dependencies = [ "uuid 0.8.2", ] -[[package]] -name = "external-call-contracts" -version = "0.0.0" -dependencies = [ - "alloy", - "alloy-primitives 0.7.6", - "e2e", - "eyre", - "mini-alloc", - "stylus-sdk", - "tokio", -] - [[package]] name = "eyre" version = "0.6.12" @@ -1597,7 +1545,7 @@ name = "function-calls" version = "0.0.0" dependencies = [ "alloy", - "alloy-primitives 0.7.6", + "alloy-primitives", "e2e", "eyre", "mini-alloc", @@ -1668,7 +1616,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -2269,7 +2217,7 @@ checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -2345,7 +2293,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -2386,7 +2334,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -2511,7 +2459,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -2624,6 +2572,20 @@ dependencies = [ "unarray", ] +[[package]] +name = "proxy-call-contracts" +version = "0.0.0" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "mini-alloc", + "pyth-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2648,7 +2610,7 @@ dependencies = [ name = "pyth-stylus" version = "0.0.1" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-types", "mini-alloc", "motsu", @@ -3138,7 +3100,7 @@ checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3314,7 +3276,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3323,7 +3285,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fd02e91dffe7b73df84a861c992494d6b72054bc9a17fe73e147e34e9a64ef3" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-types", "cfg-if 1.0.0", "convert_case 0.6.0", @@ -3342,7 +3304,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26042693706e29fb7e3cf3d71c99534ac97fca98b6f81ba77ab658022ab2e210" dependencies = [ - "alloy-primitives 0.7.6", + "alloy-primitives", "alloy-sol-types", "cfg-if 1.0.0", "derivative", @@ -3371,9 +3333,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3" dependencies = [ "proc-macro2", "quote", @@ -3389,7 +3351,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3443,7 +3405,7 @@ checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3505,7 +3467,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3624,7 +3586,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -3705,12 +3667,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "url" version = "2.5.2" @@ -3808,7 +3764,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", "wasm-bindgen-shared", ] @@ -3865,7 +3821,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4288,7 +4244,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] [[package]] @@ -4308,5 +4264,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.83", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index f958027d99..d826489dc9 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,7 +1,7 @@ [workspace] -members = ["contracts", "examples/external-call-contracts","examples/function-calls"] +members = ["contracts", "examples/proxy-call-contracts","examples/function-calls"] default-members = [ - "contracts", "examples/external-call-contracts","examples/function-calls" + "contracts", "examples/proxy-call-contracts","examples/function-calls" ] # Explicitly set the resolver to version 2, which is the default for packages @@ -17,6 +17,7 @@ repository = "https://github.com/pyth-network/pyth-crosschain/" version = "0.0.1" edition = "2021" + [workspace.lints.rust] missing_docs = "warn" unreachable_pub = "warn" @@ -66,13 +67,12 @@ syn = { version = "2.0.58", features = ["full"] } proc-macro2 = "1.0.79" quote = "1.0.35" - # members -pyth-stylus = { path = "contracts" } -e2e = { path = "lib/e2e" } -e2e-proc = {path = "lib/e2e-proc"} +pyth-stylus ={path ="contracts"} motsu = { path = "lib/motsu"} motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } +e2e = { path = "lib/e2e" } +e2e-proc = {path = "lib/e2e-proc"} [profile.release] codegen-units = 1 diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index 9d72c09cca..46e16222b1 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "function-calls" authors.workspace = true +edition.workspace = true license.workspace = true keywords.workspace = true repository.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index bf06524e96..e3b02ef3cf 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -2,14 +2,14 @@ extern crate alloc; use alloc::vec::Vec; - -use stylus_sdk::{ prelude::{entrypoint,public, sol_storage}, storage::StorageAddress, alloy_primitives::{FixedBytes, Uint}}; +use stylus_sdk::{ prelude::{entrypoint,public, sol_storage}, alloy_primitives:: Uint}; use pyth_stylus::pyth::functions::get_price_no_older_than; sol_storage! { #[entrypoint] struct FunctionCallsExample { address pyth_address; + bytes32 price_id; } } @@ -17,8 +17,7 @@ sol_storage! { #[public] impl FunctionCallsExample { pub fn check_price(&mut self) -> Result<(), Vec> { - let price = get_price_no_older_than(self, self.pyth_address,FixedBytes::from("BTC"),Uint::from(10000000000))?; + let _price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; Ok(()) } - } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml new file mode 100644 index 0000000000..bedd02ca9b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "proxy-call-contracts" +authors.workspace = true +license.workspace = true +edition.workspace = true +keywords.workspace = true +repository.workspace = true +publish = false +version = "0.0.0" + +[dependencies] +alloy-primitives.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true +pyth-stylus.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true + +[features] +e2e = [] diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs new file mode 100644 index 0000000000..89256c96c1 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs @@ -0,0 +1,28 @@ +#![cfg_attr(not(test), no_std, no_main)] +extern crate alloc; + +use alloc::vec::Vec; +use stylus_sdk::{ alloy_primitives:: Uint, console, prelude::{entrypoint,public, sol_storage}}; +use pyth_stylus::pyth::pyth_contract::PythContract; + +sol_storage! { + #[entrypoint] + struct ProxyCallsExample { + bytes32 price_id; + address pyth_address; + + #[borrow] + PythContract _pyth; + } +} + + +#[public] +#[inherit(PythContract)] +impl ProxyCallsExample { + pub fn check_price(&mut self) { + console!("test") + // let _price = self._pyth.get_price_no_older_than(self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; + // Ok(()) + } +} \ No newline at end of file From 568ba569c1564a14e5bca44fb42ea5ef5f37414d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 21:39:39 +0000 Subject: [PATCH 036/160] chore: Added beches --- .../ethereum/sdk/stylus/benches/Cargo.toml | 19 +++++++++++++++++++ .../ethereum/sdk/stylus/benches/src/main.rs | 3 +++ 2 files changed, 22 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/benches/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/benches/src/main.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml new file mode 100644 index 0000000000..4a892d2a37 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "benches" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +pyth-stylus.workspace = true +alloy-primitives = { workspace = true, features = ["tiny-keccak"] } +alloy.workspace = true +tokio.workspace = true +futures.workspace = true +eyre.workspace = true +koba.workspace = true +e2e.workspace = true +serde = "1.0.203" +keccak-const = "0.2.0" diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs new file mode 100644 index 0000000000..76d8db4ba7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -0,0 +1,3 @@ +fn main(){ + print_ln!("Hello, world!"); +} \ No newline at end of file From 72a9b5b22ded6274d8c74d07455fcba583d007db Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 21:40:18 +0000 Subject: [PATCH 037/160] Lib Changes --- .../ethereum/sdk/stylus/lib/crypto/Cargo.toml | 8 ++++---- .../ethereum/sdk/stylus/lib/crypto/README.md | 5 ----- .../ethereum/sdk/stylus/lib/crypto/src/hash.rs | 12 ++++++------ .../ethereum/sdk/stylus/lib/crypto/src/merkle.rs | 6 +++--- .../ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml | 3 ++- target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml | 5 +++-- target_chains/ethereum/sdk/stylus/lib/e2e/README.md | 5 ----- .../ethereum/sdk/stylus/lib/e2e/src/account.rs | 6 ++++-- .../ethereum/sdk/stylus/lib/e2e/src/deploy.rs | 6 +++++- .../ethereum/sdk/stylus/lib/e2e/src/system.rs | 4 ++-- 10 files changed, 29 insertions(+), 31 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml index cf119fc923..777aeacaed 100644 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "openzeppelin-crypto" -categories = ["cryptography", "algorithms", "no-std", "wasm"] -description = "Cryptography Utilities" +description = "Cryptographic Utilities" edition.workspace = true -keywords.workspace = true +categories = ["cryptography", "algorithms", "no-std", "wasm"] +keywords = ["crypto", "web3", "blockchain", "merkle"] license.workspace = true repository.workspace = true -version = "0.1.0-rc" +version.workspace = true [dependencies] mini-alloc.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/README.md b/target_chains/ethereum/sdk/stylus/lib/crypto/README.md index 081bdb1927..9b06a9a2ed 100644 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/README.md +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/README.md @@ -26,9 +26,4 @@ the [Cargo.toml](./Cargo.toml) file. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs index 65a641108c..b8f98fd5d2 100644 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs @@ -142,16 +142,16 @@ where /// Sort the pair `(a, b)` and hash the result with `state`. Frequently used /// when working with merkle proofs. #[inline] -pub fn commutative_hash_pair(mut a: H, mut b: H, state: S) -> S::Output +pub fn commutative_hash_pair(a: &H, b: &H, state: S) -> S::Output where H: Hash + PartialOrd, S: Hasher, { if a > b { - core::mem::swap(&mut a, &mut b); + hash_pair(b, a, state) + } else { + hash_pair(a, b, state) } - - hash_pair(&a, &b, state) } #[cfg(all(test, feature = "std"))] @@ -185,8 +185,8 @@ mod tests { let a = [1u8].as_slice(); let b = [2u8].as_slice(); - let r1 = commutative_hash_pair(a, b, builder.build_hasher()); - let r2 = commutative_hash_pair(b, a, builder.build_hasher()); + let r1 = commutative_hash_pair(&a, &b, builder.build_hasher()); + let r2 = commutative_hash_pair(&b, &a, builder.build_hasher()); assert_eq!(r1, r2); } } diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs index 74063f575a..9e6ee91ca5 100644 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs +++ b/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs @@ -183,7 +183,7 @@ where builder: &B, ) -> bool { for &hash in proof { - leaf = commutative_hash_pair(leaf, hash, builder.build_hasher()); + leaf = commutative_hash_pair(&leaf, &hash, builder.build_hasher()); } leaf == root @@ -304,7 +304,7 @@ where proof_pos += 1; }; - let hash = commutative_hash_pair(a, *b, builder.build_hasher()); + let hash = commutative_hash_pair(&a, b, builder.build_hasher()); hashes.push(hash); } @@ -412,7 +412,7 @@ mod tests { assert!(verification); let builder = KeccakBuilder.build_hasher(); - let no_such_leaf = commutative_hash_pair(leaf_a, leaf_b, builder); + let no_such_leaf = commutative_hash_pair(&leaf_a, &leaf_b, builder); let proof = &proof[1..]; let verification = Verifier::verify(proof, root, no_such_leaf); assert!(verification); diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml index c89b39f34e..aaa76ad183 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml @@ -2,10 +2,11 @@ name = "e2e-proc" description = "End-to-end Testing Procedural Macros" version = "0.1.0" +categories = ["development-tools::testing", "cryptography::cryptocurrencies"] +keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] authors.workspace = true edition.workspace = true license.workspace = true -keywords.workspace = true repository.workspace = true [dependencies] diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml index 6d6cf933c7..84a9c6df0c 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml @@ -2,10 +2,11 @@ name = "e2e" description = "End-to-end Testing for Stylus" version = "0.1.0" +categories = ["development-tools::testing", "cryptography::cryptocurrencies"] +keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] authors.workspace = true edition.workspace = true license.workspace = true -keywords.workspace = true repository.workspace = true [dependencies] @@ -15,7 +16,7 @@ eyre.workspace = true regex.workspace = true once_cell.workspace = true koba.workspace = true -e2e-proc = { path = "../e2e-proc" } +e2e-proc.workspace = true toml = "0.8.13" [lints] diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/README.md b/target_chains/ethereum/sdk/stylus/lib/e2e/README.md index f2c4ad1e83..dbd5c850f1 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/README.md +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/README.md @@ -145,9 +145,4 @@ our [code of conduct] and [contribution guidelines]. ## Security -> [!WARNING] -> This project is still in a very early and experimental phase. It has never -> been audited nor thoroughly reviewed for security vulnerabilities. Do not use -> in production. - Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs index 5e0e9559db..83d4cd2630 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs @@ -13,6 +13,8 @@ use crate::{ system::{fund_account, Wallet, RPC_URL_ENV_VAR_NAME}, }; +const DEFAULT_FUNDING_ETH: u32 = 100; + /// Type that corresponds to a test account. #[derive(Clone, Debug)] pub struct Account { @@ -23,7 +25,7 @@ pub struct Account { } impl Account { - /// Create a new account. + /// Create a new account with a default funding of [`DEFAULT_FUNDING_ETH`]. /// /// # Errors /// @@ -103,7 +105,7 @@ impl AccountFactory { let signer = PrivateKeySigner::random(); let addr = signer.address(); - fund_account(addr, "100")?; + fund_account(addr, DEFAULT_FUNDING_ETH)?; let rpc_url = std::env::var(RPC_URL_ENV_VAR_NAME) .expect("failed to load RPC_URL var from env") diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs index a307472ea9..e65aef6eb2 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use alloy::{rpc::types::TransactionReceipt, sol_types::SolConstructor}; use koba::config::Deploy; @@ -45,11 +47,13 @@ impl Deployer { let pkg = Crate::new()?; let wasm_path = pkg.wasm; let sol_path = pkg.manifest_dir.join("src/constructor.sol"); + let sol = + if Path::new(&sol_path).exists() { Some(sol_path) } else { None }; let config = Deploy { generate_config: koba::config::Generate { wasm: wasm_path.clone(), - sol: Some(sol_path), + sol, args: self.ctr_args, legacy: false, }, diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs index aef9ac3ecb..5d90cd8be5 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs @@ -61,7 +61,7 @@ pub fn provider() -> Provider { } /// Send `amount` eth to `address` in the nitro-tesnode. -pub fn fund_account(address: Address, amount: &str) -> eyre::Result<()> { +pub fn fund_account(address: Address, amount: u32) -> eyre::Result<()> { let node_script = get_node_path()?.join("test-node.bash"); if !node_script.exists() { bail!("Test nitro node wasn't setup properly. Try to setup it first with `./scripts/nitro-testnode.sh -i -d`") @@ -73,7 +73,7 @@ pub fn fund_account(address: Address, amount: &str) -> eyre::Result<()> { .arg("--to") .arg(format!("address_{address}")) .arg("--ethamount") - .arg(amount) + .arg(amount.to_string()) .output()?; if !output.status.success() { From ec4d75b7829964f7846bc574d980745eb470a992 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 21:40:42 +0000 Subject: [PATCH 038/160] chore:Scripts modifications --- target_chains/ethereum/sdk/stylus/scripts/bench.sh | 11 +++++++++-- .../ethereum/sdk/stylus/scripts/nitro-testnode.sh | 13 ++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 8878daec23..1801d6c3df 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -5,9 +5,16 @@ MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" cd .. -NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly} +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort export RPC_URL=http://localhost:8547 -cargo run --release -p benches + +# No need to compile benchmarks with `--release` +# since this only runs the benchmarking code and the contracts have already been compiled with `--release` +cargo run -p benches + +echo "NOTE: To measure non cached contract's gas usage correctly, + benchmarks should run on a clean instance of the nitro test node." +echo echo "Finished running benches!" diff --git a/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh index 815323c242..4e807379da 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh @@ -18,7 +18,14 @@ do shift ;; -q|--quit) - docker container stop $(docker container ls -q --filter name=nitro-testnode) + NITRO_CONTAINERS=$(docker container ls -q --filter name=nitro-testnode) + + if [ -z "$NITRO_CONTAINERS" ]; then + echo "No nitro-testnode containers running" + else + docker container stop $NITRO_CONTAINERS || exit + fi + exit 0 ;; *) @@ -43,8 +50,8 @@ then git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git cd ./nitro-testnode || exit - # `release` branch. - git checkout 8cb6b84e31909157d431e7e4af9fb83799443e00 || exit + git pull origin release --recurse-submodules + git checkout d4244cd5c2cb56ca3d11c23478ef9642f8ebf472 || exit ./test-node.bash --no-run --init || exit fi From 047a73d86046be2a951fee777bac22f0a2fecfee Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 23:19:40 +0000 Subject: [PATCH 039/160] chore : fuction call and proxy call --- target_chains/ethereum/sdk/stylus/.gitignore | 2 +- .../ethereum/sdk/stylus/.linkspector.yml | 7 + target_chains/ethereum/sdk/stylus/Cargo.lock | 951 +++++++++--------- target_chains/ethereum/sdk/stylus/Cargo.toml | 22 +- target_chains/ethereum/sdk/stylus/SECURITY.md | 2 + .../ethereum/sdk/stylus/contracts/Cargo.toml | 1 - .../ethereum/sdk/stylus/contracts/src/lib.rs | 2 +- .../sdk/stylus/contracts/src/pyth/mod.rs | 2 +- .../contracts/src/pyth/pyth_contract.rs | 2 +- .../stylus/examples/function-calls/Cargo.toml | 1 - .../function-calls/tests/function_call.rs | 9 +- .../examples/proxy-call-contracts/Cargo.toml | 1 - 12 files changed, 529 insertions(+), 473 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/.linkspector.yml create mode 100644 target_chains/ethereum/sdk/stylus/SECURITY.md diff --git a/target_chains/ethereum/sdk/stylus/.gitignore b/target_chains/ethereum/sdk/stylus/.gitignore index 27b33784a5..28adb4fad7 100644 --- a/target_chains/ethereum/sdk/stylus/.gitignore +++ b/target_chains/ethereum/sdk/stylus/.gitignore @@ -8,4 +8,4 @@ docs/build/ **/.DS_Store -**/nitro-testnode \ No newline at end of file +**/nitro-testnode diff --git a/target_chains/ethereum/sdk/stylus/.linkspector.yml b/target_chains/ethereum/sdk/stylus/.linkspector.yml new file mode 100644 index 0000000000..5f26c71861 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.linkspector.yml @@ -0,0 +1,7 @@ +dirs: + - docs/modules/ROOT/pages/ + +# There's a bug in linkspector that causes it to only allow one file extension type +# when using it with directories. +fileExtensions: + - adoc diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 9e6d1d7824..7b8761285b 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "gimli 0.31.1", + "gimli 0.29.0", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" @@ -39,6 +39,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -88,9 +100,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da374e868f54c7f4ad2ad56829827badca388efd645f8cf5fccc61c2b5343504" +checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" dependencies = [ "alloy-eips", "alloy-primitives", @@ -102,9 +114,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc6957ff706f9e5f6fd42f52a93e4bce476b726c92d077b348de28c4a76730c" +checksum = "0c26b7d34cb76f826558e9409a010e25257f7bfb5aa5e3dd0042c564664ae159" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -145,14 +157,14 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 0.6.13", ] [[package]] name = "alloy-eips" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76ecab54890cdea1e4808fc0891c7e6cfcf71fe1a9fe26810c7280ef768f4ed" +checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -165,9 +177,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca15afde1b6d15e3fc1c97421262b1bbb37aee45752e3c8b6d6f13f776554ff" +checksum = "48450f9c6f0821c1eee00ed912942492ed4f11dd69532825833de23ecc7a2256" dependencies = [ "alloy-primitives", "alloy-serde", @@ -188,9 +200,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6f34930b7e3e2744bcc79056c217f00cb2abb33bc5d4ff88da7623c5bb078b" +checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" dependencies = [ "alloy-primitives", "serde", @@ -201,9 +213,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f6895fc31b48fa12306ef9b4f78b7764f8bd6d7d91cdb0a40e233704a0f23f" +checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" dependencies = [ "alloy-consensus", "alloy-eips", @@ -244,9 +256,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c538bfa893d07e27cb4f3c1ab5f451592b7c526d511d62b576a2ce59e146e4a" +checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -276,9 +288,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "a43b18702501396fa9bcdeecd533bc85fac75150d308fc0f6800a01e6234a003" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -287,20 +299,20 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "alloy-rpc-client" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba31bae67773fd5a60020bea900231f8396202b7feca4d0c70c6b59308ab4a8" +checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -319,9 +331,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184a7a42c7ba9141cc9e76368356168c282c3bc3d9e5d78f3556bdfe39343447" +checksum = "0dfa1dd3e0bc3a3d89744fba8d1511216e83257160da2cd028a18b7d9c026030" dependencies = [ "alloy-rpc-types-eth", "alloy-serde", @@ -329,9 +341,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4123ee21f99ba4bd31bfa36ba89112a18a500f8b452f02b35708b1b951e2b9" +checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -347,9 +359,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9416c52959e66ead795a11f4a86c248410e9e368a0765710e57055b8a1774dd6" +checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" dependencies = [ "alloy-primitives", "serde", @@ -358,9 +370,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b33753c09fa1ad85e5b092b8dc2372f1e337a42e84b9b4cff9fede75ba4adb32" +checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" dependencies = [ "alloy-primitives", "async-trait", @@ -372,9 +384,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc9c26fe6c6f1bad818c9a976de9044dd12e1f75f1f156a801ee3e8148c1b6" +checksum = "87db68d926887393a1d0f9c43833b44446ea29d603291e7b20e5d115f31aa4e3" dependencies = [ "alloy-consensus", "alloy-network", @@ -399,7 +411,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -412,11 +424,11 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.6.0", + "indexmap 2.2.6", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", "syn-solidity", "tiny-keccak", ] @@ -434,17 +446,17 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.83", + "syn 2.0.68", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" dependencies = [ - "winnow", + "winnow 0.6.13", ] [[package]] @@ -462,9 +474,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b51a291f949f755e6165c3ed562883175c97423703703355f4faa4b7d0a57c" +checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" dependencies = [ "alloy-json-rpc", "base64", @@ -475,15 +487,14 @@ dependencies = [ "thiserror", "tokio", "tower", - "tracing", "url", ] [[package]] name = "alloy-transport-http" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d65871f9f1cafe1ed25cde2f1303be83e6473e995a2d56c275ae4fcce6119c" +checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -496,9 +507,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", @@ -511,33 +522,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -577,7 +588,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "zeroize", ] @@ -669,15 +680,15 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-stream" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -686,24 +697,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -714,28 +725,28 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", + "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets", ] [[package]] @@ -756,6 +767,22 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "benches" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "futures", + "keccak-const", + "koba", + "pyth-stylus", + "serde", + "tokio", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -806,9 +833,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.13" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" dependencies = [ "cc", "glob", @@ -878,9 +905,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] @@ -893,27 +920,23 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "c-kzg" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +checksum = "cdf100c4cea8f207e883ff91ca886d621d8a166cb04971dfaa9bb8fd99ed95df" dependencies = [ "blst", "cc", "glob", "hex", "libc", - "once_cell", "serde", ] [[package]] name = "cc" -version = "1.1.31" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" -dependencies = [ - "shlex", -] +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" [[package]] name = "cfg-if" @@ -939,9 +962,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -949,9 +972,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -961,33 +984,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "const-hex" -version = "1.13.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1029,9 +1052,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "corosensei" @@ -1048,9 +1071,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1202,9 +1225,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -1212,26 +1235,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -1277,8 +1300,8 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.83", + "rustc_version 0.4.0", + "syn 2.0.68", ] [[package]] @@ -1304,9 +1327,9 @@ dependencies = [ [[package]] name = "dunce" -version = "1.0.5" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "e2e" @@ -1328,7 +1351,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -1392,23 +1415,23 @@ dependencies = [ [[package]] name = "enumset" -version = "1.1.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.10.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -1467,9 +1490,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastrlp" @@ -1510,12 +1533,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" - [[package]] name = "foreign-types" version = "0.3.2" @@ -1562,9 +1579,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1577,9 +1594,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1587,15 +1604,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1604,38 +1621,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1699,9 +1716,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -1726,7 +1743,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -1734,16 +1751,9 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" dependencies = [ + "ahash 0.8.11", "allocator-api2", - "equivalent", - "foldhash", ] [[package]] @@ -1795,9 +1805,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", "http", @@ -1818,15 +1828,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "hyper" -version = "1.5.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -1859,9 +1869,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-channel", @@ -1872,6 +1882,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", "tower-service", "tracing", ] @@ -1930,12 +1941,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.14.5", ] [[package]] @@ -1949,15 +1960,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -1985,18 +1996,18 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "k256" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if 1.0.0", "ecdsa", @@ -2016,9 +2027,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -2064,9 +2075,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -2098,11 +2109,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.5" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.14.5", ] [[package]] @@ -2171,23 +2182,22 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] name = "mio" -version = "1.0.2" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "hermit-abi", "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2217,7 +2227,7 @@ checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -2278,44 +2288,44 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "object" -version = "0.36.5" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", @@ -2334,7 +2344,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -2345,9 +2355,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -2357,9 +2367,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.1.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "parity-scale-codec" @@ -2407,7 +2417,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.5", ] [[package]] @@ -2433,9 +2443,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -2444,22 +2454,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -2486,18 +2496,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primitive-types" @@ -2512,11 +2519,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -2545,9 +2552,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -2608,7 +2615,7 @@ dependencies = [ [[package]] name = "pyth-stylus" -version = "0.0.1" +version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -2625,9 +2632,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2699,9 +2706,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.6.0", ] @@ -2720,9 +2727,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -2732,9 +2739,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -2743,9 +2750,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "region" @@ -2770,9 +2777,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64", "bytes", @@ -2804,7 +2811,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", + "winreg", ] [[package]] @@ -2819,9 +2826,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" dependencies = [ "bitvec", "bytecheck", @@ -2833,14 +2840,14 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.11.0", + "uuid 1.9.1", ] [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ "proc-macro2", "quote", @@ -2910,18 +2917,18 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver 1.0.23", ] [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", "errno", @@ -2932,24 +2939,25 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ + "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -2980,11 +2988,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3027,9 +3035,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -3040,9 +3048,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -3074,9 +3082,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.213" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -3094,32 +3102,31 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" dependencies = [ "itoa", - "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -3159,20 +3166,14 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" dependencies = [ "cc", "cfg-if 1.0.0", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3194,9 +3195,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "slab" @@ -3276,7 +3277,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -3333,9 +3334,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.83" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -3344,14 +3345,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -3359,9 +3360,6 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] [[package]] name = "tap" @@ -3371,41 +3369,40 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", "fastrand", - "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -3428,9 +3425,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -3443,31 +3440,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", + "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -3482,9 +3480,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3494,9 +3492,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -3507,36 +3505,47 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.14", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.13", ] [[package]] @@ -3549,6 +3558,7 @@ dependencies = [ "futures-util", "pin-project", "pin-project-lite", + "tokio", "tower-layer", "tower-service", "tracing", @@ -3556,15 +3566,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -3586,7 +3596,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -3612,9 +3622,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uint" @@ -3636,36 +3646,36 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "url" @@ -3696,9 +3706,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" [[package]] name = "valuable" @@ -3714,9 +3724,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" @@ -3744,27 +3754,26 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if 1.0.0", - "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", "wasm-bindgen-shared", ] @@ -3793,9 +3802,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3805,9 +3814,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3815,31 +3824,30 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.219.1" +version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" +checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" dependencies = [ "leb128", - "wasmparser 0.219.1", ] [[package]] @@ -3889,7 +3897,7 @@ dependencies = [ "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.95.0", + "wasmparser", "winapi", ] @@ -3977,21 +3985,11 @@ dependencies = [ "url", ] -[[package]] -name = "wasmparser" -version = "0.219.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" -dependencies = [ - "bitflags 2.6.0", - "indexmap 2.6.0", -] - [[package]] name = "wast" -version = "219.0.1" +version = "212.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" +checksum = "4606a05fb0aae5d11dd7d8280a640d88a63ee019360ba9be552da3d294b8d1f5" dependencies = [ "bumpalo", "leb128", @@ -4002,18 +4000,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.219.1" +version = "1.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" +checksum = "c74ca7f93f11a5d6eed8499f2a8daaad6e225cab0151bc25a091fff3b987532f" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -4053,36 +4051,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.33.0" @@ -4096,45 +4064,66 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows-targets", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -4144,9 +4133,15 @@ checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -4156,15 +4151,21 @@ checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -4174,9 +4175,15 @@ checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -4186,15 +4193,27 @@ checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -4204,19 +4223,44 @@ checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.20" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" @@ -4228,23 +4272,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] [[package]] @@ -4264,5 +4307,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.83", + "syn 2.0.68", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index d826489dc9..42dff9812c 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,7 +1,15 @@ [workspace] -members = ["contracts", "examples/proxy-call-contracts","examples/function-calls"] +members = [ + "contracts", + "examples/function-calls", + "examples/proxy-call-contracts", + "benches" +] default-members = [ - "contracts", "examples/proxy-call-contracts","examples/function-calls" + "contracts", + "examples/function-calls", + "examples/proxy-call-contracts", + "benches" ] # Explicitly set the resolver to version 2, which is the default for packages @@ -11,12 +19,10 @@ resolver = "2" [workspace.package] authors = ["Ifechukwu Daniel"] -license = "MIT" -keywords = ["arbitrum", "ethereum", "stylus"] -repository = "https://github.com/pyth-network/pyth-crosschain/" -version = "0.0.1" edition = "2021" - +license = "MIT" +repository = "https://github.com/OpenZeppelin/rust-contracts-stylus" +version = "0.1.0" [workspace.lints.rust] missing_docs = "warn" @@ -68,11 +74,11 @@ proc-macro2 = "1.0.79" quote = "1.0.35" # members -pyth-stylus ={path ="contracts"} motsu = { path = "lib/motsu"} motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } e2e = { path = "lib/e2e" } e2e-proc = {path = "lib/e2e-proc"} +pyth-stylus ={path ="contracts"} [profile.release] codegen-units = 1 diff --git a/target_chains/ethereum/sdk/stylus/SECURITY.md b/target_chains/ethereum/sdk/stylus/SECURITY.md new file mode 100644 index 0000000000..3ebfc6fb66 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/SECURITY.md @@ -0,0 +1,2 @@ +# Security + diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index 415e72bafa..da886c7740 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -2,7 +2,6 @@ name = "pyth-stylus" authors.workspace = true license.workspace = true -keywords.workspace = true repository.workspace = true version.workspace = true edition.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 969a2e7f8e..c5bec36839 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -5,7 +5,7 @@ extern crate alloc; #[global_allocator] - static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; pub mod utils; pub mod pyth; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index fcbd826300..8e47444d53 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,7 +1,7 @@ mod errors; mod events; mod ipyth; -mod types; +pub mod types; mod mock; pub mod functions; pub mod pyth_contract; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index 22cf355f41..3ddbcfd79c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -16,7 +16,7 @@ use crate::pyth::functions::{ }; sol_storage! { - struct PythContract { + pub struct PythContract { address _ipyth; } } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index 46e16222b1..99c8d671a5 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -3,7 +3,6 @@ name = "function-calls" authors.workspace = true edition.workspace = true license.workspace = true -keywords.workspace = true repository.workspace = true publish = false version = "0.0.0" diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs index cd4edfde11..75cb062c73 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs @@ -5,11 +5,12 @@ use std::print; use abi::Pyth; use alloy::primitives::{fixed_bytes, uint, Address, Bytes, U256}; use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; +//use rand::Rng; -fn random_token_id() -> U256 { - let num: u32 = rand::random(); - U256::from(num) -} +// fn random_token_id() -> U256 { +// let num: u32 = rand::random(); +// U256::from(num) +// } #[e2e::test] async fn constructs(alice: Account) -> eyre::Result<()> { diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml index bedd02ca9b..7519ee9b1f 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml @@ -3,7 +3,6 @@ name = "proxy-call-contracts" authors.workspace = true license.workspace = true edition.workspace = true -keywords.workspace = true repository.workspace = true publish = false version = "0.0.0" From ded7cb48f5836d11f172878f25714a2613dba788 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 23:20:49 +0000 Subject: [PATCH 040/160] chore: added workflow --- .../stylus/.github/workflows/check-links.yml | 37 ++++++ .../.github/workflows/check-publish.yml | 44 +++++++ .../stylus/.github/workflows/check-wasm.yml | 38 ++++++ .../sdk/stylus/.github/workflows/check.yml | 113 ++++++++++++++++ .../stylus/.github/workflows/e2e-tests.yml | 55 ++++++++ .../stylus/.github/workflows/gas-bench.yml | 48 +++++++ .../sdk/stylus/.github/workflows/nostd.yml | 32 +++++ .../sdk/stylus/.github/workflows/test.yml | 123 ++++++++++++++++++ 8 files changed, 490 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/test.yml diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml new file mode 100644 index 0000000000..17b354f1fa --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml @@ -0,0 +1,37 @@ +name: Check Links +# This workflow checks that all links in the documentation are valid. +# It does this for antora docs(adoc) and markdown files. +# We prefer lycheeverse because it is faster, but doesn't support adoc files yet(https://github.com/lycheeverse/lychee/issues/291) +# Because of that, we use linkspector for adoc files and lychee for md files. +on: + push: + branches: [ main, v* ] + pull_request: + branches: [ main, v* ] + +jobs: + check-links-md: + name: Check Markdown Links + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Link Checker + uses: lycheeverse/lychee-action@v2 + with: + args: --no-progress './**/*.md' + fail: true + + check-links-adoc: + name: Check AsciiDoc Links + runs-on: ubuntu-latest + # We only run the AsciiDoc link checker on v* branches because: + # 1. The main branch may contain documentation that is not yet published. + if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') + steps: + - uses: actions/checkout@v4 + + - name: Run linkspector + uses: umbrelladocs/action-linkspector@v1 + with: + fail_on_error: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml new file mode 100644 index 0000000000..16956b9fd0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml @@ -0,0 +1,44 @@ +name: check-publish +# This workflow checks if the libraries can be published on crates.io. +permissions: + contents: read +on: + push: + branches: [ main ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + check-publish: + name: Check publish on crates.io + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: set up rust + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + target: wasm32-unknown-unknown + components: rust-src + toolchain: nightly-2024-01-01 + + - uses: Swatinem/rust-cache@v2 + + - name: check motsu-proc + run: cargo publish -p motsu-proc --dry-run + + - name: check motsu + run: cargo publish -p motsu --dry-run + + - name: check openzeppelin-crypto + run: cargo publish -p openzeppelin-crypto --target wasm32-unknown-unknown --dry-run + + - name: check openzeppelin-stylus-proc + run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --dry-run + + - name: check openzeppelin-stylus + run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml new file mode 100644 index 0000000000..f452c4fa20 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml @@ -0,0 +1,38 @@ +name: check-wasm +# This workflow checks that the compiled wasm binary of every example contract +# can be deployed to Arbitrum Stylus. +permissions: + contents: read +on: + push: + branches: [ main ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + check-wasm: + name: Check WASM binary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: set up rust + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + target: wasm32-unknown-unknown + components: rust-src + toolchain: nightly-2024-01-01 + + - uses: Swatinem/rust-cache@v2 + + - name: install cargo-stylus + run: cargo install cargo-stylus@0.5.1 + + - name: run wasm check + run: | + export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} + ./scripts/check-wasm.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml new file mode 100644 index 0000000000..a8ee0f6005 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml @@ -0,0 +1,113 @@ +name: check +# This workflow runs whenever a PR is opened or updated, or a commit is pushed +# to main. It runs several checks: +# - fmt: checks that the code is formatted according to `rustfmt`. +# - clippy: checks that the code does not contain any `clippy` warnings. +# - doc: checks that the code can be documented without errors. +# - hack: check combinations of feature flags. +# - typos: checks for typos across the repo. +permissions: + contents: read +# This configuration allows maintainers of this repo to create a branch and +# pull request based on the new branch. Restricting the push trigger to the +# main branch ensures that the PR only gets built once. +on: + push: + branches: [ main ] + pull_request: +# If new code is pushed to a PR branch, then cancel in progress workflows for +# that PR. Ensures that we don't waste CI time, and returns results quicker. +# https://github.com/jonhoo/rust-ci-conf/pull/5 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + fmt: + runs-on: ubuntu-latest + name: nightly / fmt + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + # We run in nightly to make use of some features only available there. + # Check out `rustfmt.toml` to see which ones. + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + - name: cargo fmt --all --check + run: cargo fmt --all --check + clippy: + runs-on: ubuntu-latest + name: ${{ matrix.toolchain }} / clippy + permissions: + contents: read + checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta + # channels. + toolchain: [ stable, beta ] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} + doc: + # Run docs generation on nightly rather than stable. This enables features + # like https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html + # which allows an API be documented as only available in some specific + # platforms. + runs-on: ubuntu-latest + name: nightly / doc + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + hack: + # `cargo-hack` checks combinations of feature flags to ensure that features + # are all additive which is required for feature unification. + runs-on: ubuntu-latest + name: ubuntu / stable / features + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + target: wasm32-unknown-unknown + - name: cargo install cargo-hack + uses: taiki-e/install-action@cargo-hack + # Intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 + # `--feature-powerset` runs for every combination of features. Note that + # target in this context means one of `--lib`, `--bin`, etc, and not the + # target triple. + - name: cargo hack + run: cargo hack check --feature-powerset --depth 2 --release --target wasm32-unknown-unknown --skip std --workspace --exclude e2e --exclude basic-example-script --exclude benches + typos: + runs-on: ubuntu-latest + name: ubuntu / stable / typos + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Check spelling of files in the workspace + uses: crate-ci/typos@v1.26.0 diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000000..cebd03798f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml @@ -0,0 +1,55 @@ +# This workflow runs our end-to-end tests suite. +# +# It roughly follows these steps: +# - Install Rust +# - Install `cargo-stylus` +# - Install `solc` +# - Spin up `nitro-testnode` +# +# Contract deployments and account funding happen on a per-test basis. +name: e2e +permissions: + contents: read +on: + push: + branches: [ main ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + required: + name: tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: set up rust + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + target: wasm32-unknown-unknown + components: rust-src + toolchain: nightly-2024-01-01 + + - uses: Swatinem/rust-cache@v2 + with: + key: "e2e-tests" + + - name: install cargo-stylus + run: cargo install cargo-stylus@0.5.1 + + - name: install solc + run: | + curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux + sudo mv solc-static-linux /usr/bin/solc + sudo chmod a+x /usr/bin/solc + + - name: setup nitro node + run: ./scripts/nitro-testnode.sh -d -i + - name: run integration tests + run: | + export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} + ./scripts/e2e-tests.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml new file mode 100644 index 0000000000..a14bf25c3a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml @@ -0,0 +1,48 @@ +name: gas-bench +# This workflow checks that the compiled wasm binary of every example contract +# can be deployed to Arbitrum Stylus. +permissions: + contents: read +on: + push: + branches: [ main ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + required: + name: gas usage report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: set up rust + uses: dtolnay/rust-toolchain@master + id: toolchain + with: + target: wasm32-unknown-unknown + components: rust-src + toolchain: nightly-2024-01-01 + + - uses: Swatinem/rust-cache@v2 + with: + key: "gas-bench" + + - name: install cargo-stylus + run: cargo install cargo-stylus@0.5.1 + + - name: install solc + run: | + curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux + sudo mv solc-static-linux /usr/bin/solc + sudo chmod a+x /usr/bin/solc + + - name: setup nitro node + run: ./scripts/nitro-testnode.sh -d -i + - name: run benches + run: | + export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} + ./scripts/bench.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml new file mode 100644 index 0000000000..a9e1c6e819 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml @@ -0,0 +1,32 @@ +# This workflow checks whether the library is able to run without the std +# library. See `check.yml` for information about how the concurrency +# cancellation and workflow triggering works. +name: no-std +permissions: + contents: read +on: + push: + branches: [ main ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + nostd: + runs-on: ubuntu-latest + name: ${{ matrix.target }} + strategy: + matrix: + target: [ wasm32-unknown-unknown ] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: rustup target add ${{ matrix.target }} + run: rustup target add ${{ matrix.target }} + - name: cargo check + run: cargo check --release --target ${{ matrix.target }} --no-default-features diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml new file mode 100644 index 0000000000..879ec46c03 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml @@ -0,0 +1,123 @@ +name: test +# This is the main CI workflow that runs the test suite on all pushes to main +# and all pull requests. It runs the following jobs: +# - required: runs the test suite on ubuntu with stable and beta rust +# toolchains. +# - os-check: runs the test suite on mac and windows. +# - coverage: runs the test suite and collects coverage information. +# See `check.yml` for information about how the concurrency cancellation and +# workflow triggering works. +permissions: + contents: read +on: + push: + branches: [ main ] + paths-ignore: + - "**.md" + - "**.adoc" + pull_request: + paths-ignore: + - "**.md" + - "**.adoc" +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + required: + runs-on: ubuntu-latest + name: ubuntu / ${{ matrix.toolchain }} + strategy: + matrix: + # Run on stable and beta to ensure that tests won't break on the next + # version of the rust toolchain. + toolchain: [ stable, beta ] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + - name: cargo generate-lockfile + # Enable this ci template to run regardless of whether the lockfile is + # checked in or not. + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + # https://twitter.com/jonhoo/status/1571290371124260865 + - name: cargo test --locked + run: cargo test --locked --features std --all-targets + # https://github.com/rust-lang/cargo/issues/6669 + - name: cargo test --doc + run: cargo test --locked --features std --doc + os-check: + # Run cargo test on MacOS and Windows. + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} / stable + strategy: + fail-fast: false + matrix: + os: [ macos-latest ] + # Windows fails because of `stylus-proc`. + # os: [macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test + run: cargo test --locked --features std --all-targets + coverage: + # Use llvm-cov to build and collect coverage and outputs in a format that + # is compatible with codecov.io. + # + # Note that codecov as of v4 requires that CODECOV_TOKEN from + # + # https://app.codecov.io/gh///settings + # + # is set in two places on your repo: + # + # - https://github.com/jonhoo/guardian/settings/secrets/actions + # - https://github.com/jonhoo/guardian/settings/secrets/dependabot + # + # (the former is needed for codecov uploads to work with Dependabot PRs) + # + # PRs coming from forks of your repo will not have access to the token, but + # for those, codecov allows uploading coverage reports without a token. + # it's all a little weird and inconvenient. see + # + # https://github.com/codecov/feedback/issues/112 + # + # for lots of more discussion. + runs-on: ubuntu-latest + name: ubuntu / stable / coverage + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + - name: cargo install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo llvm-cov + # FIXME: Include e2e tests in coverage. + run: cargo llvm-cov --locked --features std --lcov --output-path lcov.info + - name: Record Rust version + run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" + - name: Upload to codecov.io + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,RUST From d31557a56a0d25538377b730b9dabe80eb6cc2bc Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 23:22:15 +0000 Subject: [PATCH 041/160] chore: doc pages docs --- .../stylus/docs/modules/ROOT/pages/index.adoc | 2 - .../docs/modules/ROOT/pages/utilities.adoc | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc index 01bf3f8763..4db1e5b3fb 100644 --- a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc +++ b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc @@ -4,6 +4,4 @@ *A library for secure smart contract development* written in Rust for {stylus}. -WARNING: This repo contains highly experimental code. It has never been audited nor thoroughly reviewed for security vulnerabilities. *Use at your own risk.* - NOTE: You can track our roadmap in our https://github.com/orgs/OpenZeppelin/projects/35[Github Project]. diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc new file mode 100644 index 0000000000..0d6bc56906 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc @@ -0,0 +1,43 @@ += Utilities + +The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. +For a complete list, check out the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/index.html[API Reference]. +Here are some of the more popular ones. + +[[introspection]] +== Introspection + +It's frequently helpful to know whether a contract supports an interface you'd like to use. +https://eips.ethereum.org/EIPS/eip-165[`ERC-165`] is a standard that helps do runtime interface detection. +Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: + +* https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html[`IERC165`] — this is the ERC-165 trait that defines https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`]. In order to implement ERC-165 interface detection, you should manually expose https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`] function in your contract. + +[source,rust] +---- +sol_storage! { + #[entrypoint] + struct Erc721Example { + #[borrow] + Erc721 erc721; + } +} + +#[public] +#[inherit(Erc721)] +impl Erc721Example { + pub fn supports_interface(interface_id: FixedBytes<4>) -> bool { + Erc721::supports_interface(interface_id) + } +} + +---- + +[[structures]] +== Structures + +Some use cases require more powerful data structures than arrays and mappings offered natively in alloy and the Stylus sdk. +Contracts for Stylus provides these libraries for enhanced data structure management: + +- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. +- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. From 86dfbb1ec2d7be7fd7aac1af75c5569ec08e2c74 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 23:23:02 +0000 Subject: [PATCH 042/160] chore: doc changes --- .../ethereum/sdk/stylus/docs/antora.yml | 1 - .../sdk/stylus/docs/modules/ROOT/nav.adoc | 2 + .../sdk/stylus/docs/package-lock.json | 318 +----------------- 3 files changed, 8 insertions(+), 313 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/docs/antora.yml b/target_chains/ethereum/sdk/stylus/docs/antora.yml index 524da2ac96..9c51498218 100644 --- a/target_chains/ethereum/sdk/stylus/docs/antora.yml +++ b/target_chains/ethereum/sdk/stylus/docs/antora.yml @@ -1,6 +1,5 @@ name: contracts-stylus title: Contracts for Stylus version: 0.1.0 -prerelease: '-rc' #we can remove this with the official release nav: - modules/ROOT/nav.adoc diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc index fe72c727c6..e1c24eeb7d 100644 --- a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc +++ b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc @@ -1,2 +1,4 @@ * xref:index.adoc[Overview] * xref:deploy.adoc[Deploying Contracts] + +*** xref:erc20.adoc#erc20-token-extensions[Extensions] diff --git a/target_chains/ethereum/sdk/stylus/docs/package-lock.json b/target_chains/ethereum/sdk/stylus/docs/package-lock.json index 0a016da722..63f271ded7 100644 --- a/target_chains/ethereum/sdk/stylus/docs/package-lock.json +++ b/target_chains/ethereum/sdk/stylus/docs/package-lock.json @@ -1,7 +1,7 @@ { "name": "docs", "version": "0.1.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -24,9 +24,9 @@ }, "node_modules/@openzeppelin/docs-utils": { "version": "0.1.5", - "resolved": "git+ssh://git@github.com/OpenZeppelin/docs-utils.git#971e6517ac8654231c2b11a15850f8477bc8e42b", + "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.5.tgz", + "integrity": "sha512-GfqXArKmdq8rv+hsP+g8uS1VEkvMIzWs31dCONffzmqFwJ+MOsaNQNZNXQnLRgUkzk8i5mTNDjJuxDy+aBZImQ==", "dev": true, - "license": "MIT", "dependencies": { "@frangio/servbot": "^0.2.5", "chalk": "^3.0.0", @@ -80,15 +80,12 @@ } }, "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/braces": { @@ -439,308 +436,5 @@ "node": ">=8.0" } } - }, - "dependencies": { - "@frangio/servbot": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", - "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", - "dev": true - }, - "@openzeppelin/docs-utils": { - "version": "git+ssh://git@github.com/OpenZeppelin/docs-utils.git#971e6517ac8654231c2b11a15850f8477bc8e42b", - "dev": true, - "from": "@openzeppelin/docs-utils@^0.1.2", - "requires": { - "@frangio/servbot": "^0.2.5", - "chalk": "^3.0.0", - "chokidar": "^3.5.3", - "env-paths": "^2.2.0", - "find-up": "^4.1.0", - "is-port-reachable": "^3.0.0", - "js-yaml": "^3.13.1", - "lodash.startcase": "^4.4.0", - "minimist": "^1.2.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-port-reachable": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", - "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } } } From bcd86d6587804b2e66c2814a597ad3edf9dcf44a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 24 Oct 2024 23:23:52 +0000 Subject: [PATCH 043/160] smaller chages --- .../ethereum/sdk/stylus/.github/CODEOWNERS | 1 + .../ethereum/sdk/stylus/.github/codecov.yml | 26 +++++++++++++++++++ .../sdk/stylus/.github/dependabot.yml | 15 +++++++++++ .../stylus/.github/pull_request_template.md | 25 ++++++++++++------ 4 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/.github/CODEOWNERS create mode 100644 target_chains/ethereum/sdk/stylus/.github/codecov.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/dependabot.yml diff --git a/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS b/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS new file mode 100644 index 0000000000..da7375eb96 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS @@ -0,0 +1 @@ +* @bidzyyys @qalisander diff --git a/target_chains/ethereum/sdk/stylus/.github/codecov.yml b/target_chains/ethereum/sdk/stylus/.github/codecov.yml new file mode 100644 index 0000000000..e4802e57a2 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/codecov.yml @@ -0,0 +1,26 @@ +# ref: https://docs.codecov.com/docs/codecovyml-reference +coverage: + # Hold ourselves to a high bar. + range: 95..100 + round: down + precision: 1 + status: + # ref: https://docs.codecov.com/docs/commit-status + project: + default: + # Avoid false negatives. + threshold: 1% + informational: true + patch: + default: + informational: true +# Test files aren't important for coverage. +ignore: + - "tests" + - "docs" +# Make comments less noisy. +comment: + layout: "files" + require_changes: true +github_checks: + annotations: false diff --git a/target_chains/ethereum/sdk/stylus/.github/dependabot.yml b/target_chains/ethereum/sdk/stylus/.github/dependabot.yml new file mode 100644 index 0000000000..4649a6e9b9 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + - package-ecosystem: cargo + directory: / + schedule: + interval: daily + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-patch" + - "version-update:semver-minor" diff --git a/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md b/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md index 645ad6466c..385188f3ab 100644 --- a/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md +++ b/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md @@ -1,12 +1,21 @@ -## Description + -- [ ] I have documented these changes where necessary. -- [ ] I have read the [DCO][DCO] and ensured that these changes comply. -- [ ] I assign this work under its [open source licensing][terms]. + +Resolves #??? -[DCO]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/DCO.txt -[terms]: https://github.com/OffchainLabs/stylus-hello-world/blob/main/licenses/COPYRIGHT.md +#### PR Checklist + + + +- [ ] Tests +- [ ] Documentation From 94517eee426dfe0800833259a0707fad49f3cf74 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 27 Oct 2024 21:33:35 +0000 Subject: [PATCH 044/160] chore: benches and toml changes --- target_chains/ethereum/sdk/stylus/Cargo.lock | 38 +- target_chains/ethereum/sdk/stylus/Cargo.toml | 13 +- .../ethereum/sdk/stylus/GUIDELINES.md | 328 ++++++++++++++++++ .../ethereum/sdk/stylus/benches/src/main.rs | 2 +- .../function-calls/tests/function_call.rs | 27 +- 5 files changed, 386 insertions(+), 22 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/GUIDELINES.md diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 7b8761285b..463436953e 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2213,12 +2213,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cff6390ad19dea5f6bf7011af9560c3dc5f9162fa4b1b33c0d24df18a2c7fcb" dependencies = [ "const-hex", - "motsu-proc", + "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", + "stylus-sdk", + "tiny-keccak", +] + +[[package]] +name = "motsu" +version = "0.1.0" +dependencies = [ + "const-hex", + "motsu-proc 0.1.0", "once_cell", "stylus-sdk", "tiny-keccak", ] +[[package]] +name = "motsu-proc" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "motsu 0.1.0", + "proc-macro2", + "quote", + "stylus-sdk", + "syn 2.0.68", +] + [[package]] name = "motsu-proc" version = "0.1.0" @@ -2365,6 +2389,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "openzeppelin-crypto" +version = "0.1.0" +dependencies = [ + "hex-literal", + "mini-alloc", + "rand", + "tiny-keccak", +] + [[package]] name = "owo-colors" version = "4.0.0" @@ -2620,7 +2654,7 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "mini-alloc", - "motsu", + "motsu 0.1.0-rc", "stylus-sdk", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 42dff9812c..74fb8a029d 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,15 +1,23 @@ [workspace] members = [ "contracts", + "lib/crypto", + "lib/motsu", + "lib/motsu-proc", + "lib/e2e", + "lib/e2e-proc", "examples/function-calls", "examples/proxy-call-contracts", "benches" ] default-members = [ "contracts", + "lib/crypto", + "lib/motsu", + "lib/motsu-proc", + "lib/e2e-proc", "examples/function-calls", - "examples/proxy-call-contracts", - "benches" + "examples/proxy-call-contracts" ] # Explicitly set the resolver to version 2, which is the default for packages @@ -48,6 +56,7 @@ alloy = { version = "=0.1.4", features = [ "signer-local", "getrandom", ] } +getrandom = { version = "0.2.2", features = ["custom"] } # Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need # to keep both versions for compatibility with the Stylus SDK. Once they start # using `alloy` we can remove these. diff --git a/target_chains/ethereum/sdk/stylus/GUIDELINES.md b/target_chains/ethereum/sdk/stylus/GUIDELINES.md new file mode 100644 index 0000000000..2772b3a566 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/GUIDELINES.md @@ -0,0 +1,328 @@ +# Engineering Guidelines + +## Testing + +Code must be thoroughly tested with quality unit tests. + +We defer to the [Moloch Testing Guide] for specific recommendations, though +not all of it is relevant here, since this is a Rust project. Note the +introduction: + +> Tests should be written, not only to verify correctness of the target code, +> but to be comprehensively reviewed by other programmers. Therefore, for +> mission critical Solidity code, the quality of the tests are just as +> important (if not more so) than the code itself, and should be written with +> the highest standards of clarity and elegance. + +Every addition or change to the code must come with relevant and comprehensive +tests. + +Refactors should avoid simultaneous changes to tests. + +Flaky tests are not acceptable. + +The test suite should run automatically for every change in the repository, and +in pull requests tests must pass before merging. + +The test suite coverage must be kept as close to 100% as possible, enforced in +pull requests. + +In some cases unit tests may be insufficient and complementary techniques +should be used: + +1. Property-based tests (aka. fuzzing) for math-heavy code. +2. Formal verification for state machines. + +[Moloch Testing Guide]: https://github.com/MolochVentures/moloch/tree/master/test#readme + +## Peer review + +All changes must be submitted through pull requests and go through peer code +review. + +The review must be approached by the reviewer in a similar way as if it was an +audit of the code in question (but importantly it is not a substitute for and +should not be considered an audit). + +Reviewers should enforce code and project guidelines. + +External contributions must be reviewed separately by multiple maintainers. + +## Code style + +Rust code should be written in a consistent format enforced by `rustfmt`, +following the official [The Rust Style Guide]. See below for further +[Rust Conventions](#rust-conventions). + +The code should be simple and straightforward, prioritizing readability and +understandability. Consistency and predictability should be maintained across +the codebase. In particular, this applies to naming, which should be +systematic, clear, and concise. + +Sometimes these guidelines may be broken if doing so brings significant +efficiency gains, but explanatory comments should be added. + +The following code guidelines will help make code review smoother: + +### Use of `unwrap` and `expect` + +Use `unwrap` only in either of three circumstances: + +- Based on manual static analysis, you've concluded that it's impossible for + the code to panic; so unwrapping is _safe_. An example would be: + +```rust +let list = vec![a, b, c]; +let first = list.first().unwrap(); +``` + +- The panic caused by `unwrap` would indicate a bug in the software, and it + would be impossible to continue in that case. +- The `unwrap` is part of test code, ie. `cfg!(test)` is `true`. + +In the first and second case, document `unwrap` call sites with a comment +prefixed with `SAFETY:` that explains why it's safe to unwrap, eg. + +```rust +// SAFETY: Node IDs are valid ref strings. +let r = RefString::try_from(node.to_string()).unwrap(); +``` + +Use `expect` only if the function expects certain invariants that were not met, +either due to bad inputs, or a problem with the environment; and include the +expectation in the message. For example: + +```rust +logger::init(log::Level::Debug).expect("logger must only be initialized once"); +``` + +### Module imports + +Imports are organized in groups, from least specific to more specific: + +```rust +use std::collections::HashMap; // First, `std` imports. +use std::process; +use std::time; + +use git_ref_format as format; // Then, external dependencies. +use once_cell::sync::Lazy; + +use crate::crypto::PublicKey; // Finally, local crate imports. +use crate::storage::refs::Refs; +use crate::storage::RemoteId; +``` + +This is enforced by `rustfmt`. Note that this is a `nightly` feature. + +### Variable naming + +Use short 1-letter names when the variable scope is only a few lines, or the +context is +obvious, eg. + +```rust +if let Some(e) = result.err() { +... +} +``` + +Use 1-word names for function parameters or variables that have larger scopes: + +```rust +pub fn commit(repo: &Repository, sig: &Signature) -> Result { + ... +} +``` + +Use the most descriptive names for globals: + +```rust +pub const KEEP_ALIVE_DELTA: LocalDuration = LocalDuration::from_secs(30); +``` + +### Function naming + +Stay concise. Use the function doc comment to describe what the function does, +not the name. Keep in mind functions are in the context of the parent module +and/or object and repeating that would be redundant. + +## Dependencies + +Before adding any code dependencies, check with the maintainers if this is +okay. In general, we try not to add external dependencies unless it's +necessary. Dependencies increase counter-party risk, build-time, attack +surface, and make code harder to audit. + +We also optimize for binary size, which means we try to keep generated code to +a minimum and adding dependencies is one of the biggest sources of code bloat. + +## Documentation + +For contributors, project guidelines and processes must be documented publicly. + +For users, features must be abundantly documented. Documentation should include +answers to common questions, solutions to common problems, and recommendations +for critical decisions that the user may face. + +All changes to the core codebase (excluding tests, auxiliary scripts, etc.) +must be documented in a changelog, except for purely cosmetic or documentation +changes. + +All Rust items must be documented with documentation comments so that LSPs +display information about said items. + +## Automation + +Automation should be used as much as possible to reduce the possibility of +human error and forgetfulness. + +Automations that make use of sensitive credentials must use secure secret +management, and must be strengthened against attacks such as +[those on GitHub Actions worklows]. + +Some other examples of automation are: + +- Looking for common security vulnerabilities or errors in our code (eg. + reentrancy analysis). +- Keeping dependencies up to date and monitoring for vulnerable dependencies. + +[those on GitHub Actions worklows]: https://github.com/nikitastupin/pwnhub + +### Linting & formatting + +Always check your code with the linter (`clippy`), by running: + + $ cargo clippy --tests --all-features + +And make sure your code is formatted with, using: + + $ cargo +nightly fmt + +Finally, ensure there is no trailing whitespace anywhere. + +### Running tests + +Make sure all tests are passing with: + + $ cargo test --all-features + +### Running end-to-end tests + +In order to run end-to-end (e2e) tests you need to have a specific nightly toolchain. +"Nightly" is necessary to use optimization compiler flags and have contract wasm small enough to be eligible for +deployment. + +Run the following commands to install the necessary toolchain: + +```shell +rustup install nightly-2024-01-01 +rustup component add rust-src +``` + +Also, you should have the cargo stylus tool: + +```shell +cargo install cargo-stylus +``` + +Since most of the e2e tests use [koba](https://github.com/OpenZeppelin/koba) for deploying contracts, you need to +[install](https://docs.soliditylang.org/en/latest/installing-solidity.html#) the solidity compiler (`v0.8.24`). + +To run e2e tests, you need to have a local nitro test node up and running. +Run the following command and wait till script exit successfully: + +```shell +./scripts/nitro-testnode.sh -i -d +``` + +Then you will be able to run e2e tests: + +```shell +./scripts/e2e-tests.sh +``` + +### Checking the docs + +If you make documentation changes, you may want to check whether there are any +warnings or errors: + + $ cargo doc --all-features + +## Pull requests + +Pull requests are squash-merged to keep the `main` branch history clean. The +title of the pull request becomes the commit message, which should follow +[Semantic versioning]. + +Work in progress pull requests should be submitted as Drafts and should not be +prefixed with "WIP:". + +Branch names don't matter, and commit messages within a pull request mostly +don't matter either, although they can help the review process. + +## Writing commit messages + +A properly formed git commit subject line should always be able to complete the +following sentence: + + If applied, this commit will _____ + +In addition, it should be not capitalized and _must not_ include a period. We +prefix all commits by following [Conventional Commits] guidelines. For example, +the following message is well formed: + + feat(merkle): add single-leaf proof verification + +While these ones are **not**: `add single-leaf proof verification`, +`Added single-leaf proof verification`, `Add single-leaf proof verification`, +`frob: add single-leaf proof verification`, `feat: Add single-leaf proof +verification`. + +When it comes to formatting, here's a model git commit message[1]: + + type: lower-case, short (50 chars or less) summary + + More detailed explanatory text, if necessary. Wrap it to about 72 + characters or so. In some contexts, the first line is treated as the + subject of an email and the rest of the text as the body. The blank + line separating the summary from the body is critical (unless you omit + the body entirely); tools like rebase can get confused if you run the + two together. + + Write your commit message in the imperative: "Fix bug" and not "Fixed bug" + or "Fixes bug." This convention matches up with commit messages generated + by commands like git merge and git revert. + + Further paragraphs come after blank lines. + + - Bullet points are okay, too. + - Typically a hyphen or asterisk is used for the bullet, followed by a + single space, with blank lines in between, but conventions vary here. + - Use a hanging indent. + +## Rust Conventions + +In addition to the official [The Rust Style Guide] we have a number of other +conventions that must be followed. + +- All arithmetic should be checked, matching Solidity's behavior, unless + overflow/underflow is guaranteed not to happen. Unchecked arithmetic blocks + should contain comments explaining why overflow is guaranteed not to happen. + If the reason is immediately apparent from the line above the unchecked + block, the comment may be omitted. +- Custom errors should be declared following the [EIP-6093] rationale whenever + reasonable. Also, consider the following: + + - The domain prefix should be picked in the following order: + 1. Use `ERC` if the error is a violation of an ERC specification. + 2. Use the name of the underlying component where it belongs (eg. + `Governor`, `ECDSA`, or `Timelock`). + +[The Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/ + +[EIP-6093]: https://eips.ethereum.org/EIPS/eip-6093 + +[Semantic versioning]: https://semver.org/spec/v2.0.0.html + +[Conventional Commits]: https://www.conventionalcommits.org/en/v1.0.0/ diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 76d8db4ba7..5f72e9d9d2 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -1,3 +1,3 @@ fn main(){ - print_ln!("Hello, world!"); + //print_ln!("Hello, world!"); } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs index 75cb062c73..0f4b1a2eb1 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs @@ -1,26 +1,19 @@ #![cfg(feature = "e2e")] -use std::print; - -use abi::Pyth; -use alloy::primitives::{fixed_bytes, uint, Address, Bytes, U256}; +use alloy::hex; use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; -//use rand::Rng; +use eyre::Result; -// fn random_token_id() -> U256 { -// let num: u32 = rand::random(); -// U256::from(num) -// } +mod abi; -#[e2e::test] -async fn constructs(alice: Account) -> eyre::Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; - print_ln!("Contract address: {}", contract_addr); - // let contract = Erc721::new(contract_addr, &alice.wallet); - // let Erc721::pausedReturn { paused } = contract.paused().call().await?; +// ============================================================================ +// Integration Tests: Function Calls +// ============================================================================ - // assert_eq!(false, paused); +#[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + assert_eq!(true, true); Ok(()) -} \ No newline at end of file +} From 703e43ad64acc6d71e0da0f636a59751adfa2719 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 28 Oct 2024 16:38:48 +0000 Subject: [PATCH 045/160] chore: added foundry to e2e workflow --- .../ethereum/sdk/stylus/.github/workflows/e2e-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml index cebd03798f..aa3c27b567 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml @@ -25,6 +25,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: set up rust uses: dtolnay/rust-toolchain@master @@ -33,6 +35,9 @@ jobs: target: wasm32-unknown-unknown components: rust-src toolchain: nightly-2024-01-01 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 - uses: Swatinem/rust-cache@v2 with: From 28fa93ec8b950af5fe63a34cec54a82ab992f755 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 28 Oct 2024 16:39:04 +0000 Subject: [PATCH 046/160] chore:set up benchmark --- .../ethereum/sdk/stylus/benches/src/lib.rs | 115 +++++++++++++ .../ethereum/sdk/stylus/benches/src/main.rs | 25 ++- .../ethereum/sdk/stylus/benches/src/report.rs | 157 ++++++++++++++++++ 3 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/benches/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/benches/src/report.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs new file mode 100644 index 0000000000..308dfe6d46 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs @@ -0,0 +1,115 @@ +use std::process::Command; + +use alloy::{ + primitives::Address, + rpc::types::{ + serde_helpers::WithOtherFields, AnyReceiptEnvelope, Log, + TransactionReceipt, + }, +}; +use alloy_primitives::U128; +use e2e::{Account, ReceiptExt}; +use eyre::WrapErr; +use koba::config::{Deploy, Generate, PrivateKey}; +use serde::Deserialize; + +pub mod report; + +#[derive(Debug, Deserialize)] +struct ArbOtherFields { + #[serde(rename = "gasUsedForL1")] + gas_used_for_l1: U128, + #[allow(dead_code)] + #[serde(rename = "l1BlockNumber")] + l1_block_number: String, +} + +/// Cache options for the contract. +/// `Bid(0)` will likely cache the contract on the nitro test node. +pub enum CacheOpt { + None, + Bid(u32), +} + +type ArbTxReceipt = + WithOtherFields>>; + +async fn deploy( + account: &Account, + contract_name: &str, + args: Option, + cache_opt: CacheOpt, +) -> eyre::Result
{ + let manifest_dir = + std::env::current_dir().context("should get current dir from env")?; + + let wasm_path = manifest_dir + .join("target") + .join("wasm32-unknown-unknown") + .join("release") + .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); + let sol_path = args.as_ref().map(|_| { + manifest_dir + .join("examples") + .join(contract_name) + .join("src") + .join("constructor.sol") + }); + + let pk = account.pk(); + let config = Deploy { + generate_config: Generate { + wasm: wasm_path.clone(), + sol: sol_path, + args, + legacy: false, + }, + auth: PrivateKey { + private_key_path: None, + private_key: Some(pk), + keystore_path: None, + keystore_password_path: None, + }, + endpoint: env("RPC_URL")?, + deploy_only: false, + quiet: true, + }; + + let address = koba::deploy(&config) + .await + .expect("should deploy contract") + .address()?; + + if let CacheOpt::Bid(bid) = cache_opt { + cache_contract(account, address, bid)?; + } + + Ok(address) +} + +/// Try to cache a contract on the stylus network. +/// Already cached contracts won't be cached, and this function will not return +/// an error. +/// Output will be forwarded to the child process. +fn cache_contract( + account: &Account, + contract_addr: Address, + bid: u32, +) -> eyre::Result<()> { + // We don't need a status code. + // Since it is not zero when the contract is already cached. + let _ = Command::new("cargo") + .args(["stylus", "cache", "bid"]) + .args(["-e", &env("RPC_URL")?]) + .args(["--private-key", &format!("0x{}", account.pk())]) + .arg(contract_addr.to_string()) + .arg(bid.to_string()) + .status() + .context("failed to execute `cargo stylus cache bid` command")?; + Ok(()) +} + +/// Load the `name` environment variable. +fn env(name: &str) -> eyre::Result { + std::env::var(name).wrap_err(format!("failed to load {name}")) +} diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 5f72e9d9d2..0a7044d146 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -1,3 +1,22 @@ -fn main(){ - //print_ln!("Hello, world!"); -} \ No newline at end of file +use benches::{ + report::BenchmarkReport, +}; +use futures::FutureExt; + +#[tokio::main] +async fn main() -> eyre::Result<()> { + // let report = futures::future::try_join_all([ + // // access_control::bench().boxed(), + // // erc20::bench().boxed(), + // // erc721::bench().boxed(), + // // merkle_proofs::bench().boxed(), + // ]) + // .await? + // .into_iter() + // .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); + + println!(); + //println!("{report}"); + + Ok(()) +} diff --git a/target_chains/ethereum/sdk/stylus/benches/src/report.rs b/target_chains/ethereum/sdk/stylus/benches/src/report.rs new file mode 100644 index 0000000000..49b5fe70f9 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/src/report.rs @@ -0,0 +1,157 @@ +use std::{collections::HashMap, fmt::Display}; + +use crate::{ArbOtherFields, ArbTxReceipt}; + +const SEPARATOR: &str = "::"; + +#[derive(Debug)] +pub struct FunctionReport { + sig: String, + gas: u128, +} + +impl FunctionReport { + pub(crate) fn new(receipt: (&str, ArbTxReceipt)) -> eyre::Result { + Ok(FunctionReport { + sig: receipt.0.to_owned(), + gas: get_l2_gas_used(&receipt.1)?, + }) + } +} + +#[derive(Debug)] +pub struct ContractReport { + contract: String, + functions: Vec, + functions_cached: Vec, +} + +impl ContractReport { + pub fn new(contract: &str) -> Self { + ContractReport { + contract: contract.to_owned(), + functions: vec![], + functions_cached: vec![], + } + } + + pub fn add(mut self, fn_report: FunctionReport) -> eyre::Result { + self.functions.push(fn_report); + Ok(self) + } + + pub fn add_cached( + mut self, + fn_report: FunctionReport, + ) -> eyre::Result { + self.functions_cached.push(fn_report); + Ok(self) + } + + fn signature_max_len(&self) -> usize { + let prefix_len = self.contract.len() + SEPARATOR.len(); + self.functions + .iter() + .map(|FunctionReport { sig: name, .. }| prefix_len + name.len()) + .max() + .unwrap_or_default() + } + + fn gas_max_len(&self) -> usize { + self.functions + .iter() + .map(|FunctionReport { gas, .. }| gas.to_string().len()) + .max() + .unwrap_or_default() + } + + fn gas_cached_max_len(&self) -> usize { + self.functions_cached + .iter() + .map(|FunctionReport { gas, .. }| gas.to_string().len()) + .max() + .unwrap_or_default() + } +} + +#[derive(Debug, Default)] +pub struct BenchmarkReport(Vec); + +impl BenchmarkReport { + pub fn merge_with(mut self, report: ContractReport) -> Self { + self.0.push(report); + self + } + + pub fn column_width( + &self, + column_value: impl FnMut(&ContractReport) -> usize, + header: &str, + ) -> usize { + self.0 + .iter() + .map(column_value) + .chain(std::iter::once(header.len())) + .max() + .unwrap_or_default() + } +} + +impl Display for BenchmarkReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + const HEADER_SIG: &str = "Contract::function"; + const HEADER_GAS_CACHED: &str = "Cached"; + const HEADER_GAS: &str = "Not Cached"; + + // Calculating the width of table columns. + let width1 = + self.column_width(ContractReport::signature_max_len, HEADER_SIG); + let width2 = self.column_width( + ContractReport::gas_cached_max_len, + HEADER_GAS_CACHED, + ); + let width3 = self.column_width(ContractReport::gas_max_len, HEADER_GAS); + + // Print headers for the table columns. + writeln!( + f, + "| {HEADER_SIG:width2$} | {HEADER_GAS:>width3$} |" + )?; + writeln!( + f, + "| {:->width1$} | {:->width2$} | {:->width3$} |", + "", "", "" + )?; + + // Merging a non-cached gas report with a cached one. + for report in &self.0 { + let prefix = format!("{}{SEPARATOR}", report.contract); + let gas: HashMap<_, _> = report + .functions + .iter() + .map(|func| (&*func.sig, func.gas)) + .collect(); + + for report_cached in &report.functions_cached { + let sig = &*report_cached.sig; + let gas_cached = &report_cached.gas; + let gas = gas[sig]; + + let full_sig = format!("{prefix}{sig}"); + writeln!( + f, + "| {full_sig:width2$} | {gas:>width3$} |" + )?; + } + } + + Ok(()) + } +} + +fn get_l2_gas_used(receipt: &ArbTxReceipt) -> eyre::Result { + let l2_gas = receipt.gas_used; + let arb_fields: ArbOtherFields = receipt.other.deserialize_as()?; + let l1_gas = arb_fields.gas_used_for_l1.to::(); + Ok(l2_gas - l1_gas) +} From 22bae51f59a1b03338ae7ac168ba731f53c3c597 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 28 Oct 2024 23:56:37 +0000 Subject: [PATCH 047/160] Added Benches --- .../ethereum/sdk/stylus/benches/Cargo.toml | 1 + .../sdk/stylus/benches/src/function_calls.rs | 105 ++++++++++++++++++ .../ethereum/sdk/stylus/benches/src/lib.rs | 2 + .../ethereum/sdk/stylus/benches/src/main.rs | 15 +-- 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml index 4a892d2a37..67c1180c1f 100644 --- a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -17,3 +17,4 @@ koba.workspace = true e2e.workspace = true serde = "1.0.203" keccak-const = "0.2.0" +dotenv.workspace = true \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs new file mode 100644 index 0000000000..117bcc3c2d --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -0,0 +1,105 @@ +use std::str::FromStr; + +use alloy::{ + network::{AnyNetwork, EthereumWallet}, + primitives::{Address, FixedBytes as TypeFixedBytes, fixed_bytes}, + providers::ProviderBuilder, + sol, + sol_types::{sol_data::FixedBytes, SolCall, SolConstructor}, +}; +use e2e::{receipt, Account}; +use dotenv; + +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; + +sol!( + #[sol(rpc)] + contract FunctionCall{ + function getPriceNoOlderThan() external view; + } +); + +sol!("../examples/function-calls/src/constructor.sol"); + +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("FunctionCalls"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { + let alice = Account::new().await?; + let alice_addr = alice.address(); + let alice_wallet = ProviderBuilder::new() + .network::() + .with_recommended_fillers() + .wallet(EthereumWallet::from(alice.signer.clone())) + .on_http(alice.url().parse()?); + + let bob = Account::new().await?; + let bob_addr = bob.address(); + + let contract_addr = deploy(&alice, cache_opt).await?; + + let contract = FunctionCall::new(contract_addr, &alice_wallet); + println!("contract address: {contract_addr:?}"); + // let token_1 = uint!(1_U256); + // let token_2 = uint!(2_U256); + // let token_3 = uint!(3_U256); + // let token_4 = uint!(4_U256); + + let _ = receipt!(contract.getPriceNoOlderThan())?; + // let _ = receipt!(contract.mint(alice_addr, token_3))?; + // let _ = receipt!(contract.mint(alice_addr, token_4))?; + + // IMPORTANT: Order matters! + use FunctionCall::*; + //#[rustfmt::skip] + let receipts = vec![ + (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan())?), + // (approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_2))?), + // (getApprovedCall::SIGNATURE, receipt!(contract.getApproved(token_2))?), + // (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), + // (ownerOfCall::SIGNATURE, receipt!(contract.ownerOf(token_2))?), + // (safeTransferFromCall::SIGNATURE, receipt!(contract.safeTransferFrom(alice_addr, bob_addr, token_3))?), + // (setApprovalForAllCall::SIGNATURE, receipt!(contract.setApprovalForAll(bob_addr, true))?), + // (totalSupplyCall::SIGNATURE, receipt!(contract.totalSupply())?), + // (transferFromCall::SIGNATURE, receipt!(contract.transferFrom(alice_addr, bob_addr, token_4))?), + // (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_1))?), + // (burnCall::SIGNATURE, receipt!(contract.burn(token_1))?), + ]; + + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() +} + +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ + let pyth_addr = dotenv::var("MOCK_PYTH_ADDRESS")?; + let address = Address::from_str(&pyth_addr)?; + let id= "ETH"; + let mut bytes = [0u8; 32]; + bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); + let price_id :TypeFixedBytes<32> = TypeFixedBytes::from(bytes); + println!("pyth address: {address:?} price_id: {price_id:?}"); + let args = FunctionCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; + let args = alloy::hex::encode(args.abi_encode()); + crate::deploy(account, "function_calls", Some(args), cache_opt).await +} diff --git a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs index 308dfe6d46..2a2ecf73e0 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs @@ -14,6 +14,7 @@ use koba::config::{Deploy, Generate, PrivateKey}; use serde::Deserialize; pub mod report; +pub mod function_calls; #[derive(Debug, Deserialize)] struct ArbOtherFields { @@ -48,6 +49,7 @@ async fn deploy( .join("wasm32-unknown-unknown") .join("release") .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); + println!("wasm path: {wasm_path:?}"); let sol_path = args.as_ref().map(|_| { manifest_dir .join("examples") diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 0a7044d146..4ca06a9c32 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -1,22 +1,23 @@ use benches::{ - report::BenchmarkReport, + function_calls,report::BenchmarkReport, }; use futures::FutureExt; #[tokio::main] async fn main() -> eyre::Result<()> { - // let report = futures::future::try_join_all([ + let report = futures::future::try_join_all([ + function_calls::bench().boxed(), // // access_control::bench().boxed(), // // erc20::bench().boxed(), // // erc721::bench().boxed(), // // merkle_proofs::bench().boxed(), - // ]) - // .await? - // .into_iter() - // .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); + ]) + .await? + .into_iter() + .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); println!(); - //println!("{report}"); + println!("{report}"); Ok(()) } From 85cd41cd9f7e65001016197e36e28f5c57116498 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 28 Oct 2024 23:59:03 +0000 Subject: [PATCH 048/160] chore:added setup mock pyth script to bench and e2e and logs --- .../stylus/examples/function-calls/Cargo.toml | 15 +++++----- .../function-calls/src/constructor.sol | 29 +++++++++++++++++++ .../stylus/examples/function-calls/src/lib.rs | 29 +++++++++++++++---- .../function-calls/tests/function_call.rs | 19 ------------ .../function-calls/tests/function_test.rs | 19 ++++++++++++ .../examples/proxy-call-contracts/src/lib.rs | 3 +- .../ethereum/sdk/stylus/scripts/bench.sh | 20 +++++++++++++ .../ethereum/sdk/stylus/scripts/e2e-tests.sh | 26 ++++++++++++++--- 8 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index 99c8d671a5..ca4753fe29 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -1,17 +1,16 @@ [package] -name = "function-calls" -authors.workspace = true +name = "function-calls-example" edition.workspace = true license.workspace = true repository.workspace = true publish = false -version = "0.0.0" +version.workspace = true [dependencies] +pyth-stylus.workspace = true alloy-primitives.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true -pyth-stylus.workspace = true [dev-dependencies] alloy.workspace = true @@ -19,8 +18,8 @@ eyre.workspace = true tokio.workspace = true e2e.workspace = true -[features] -e2e = [] - [lib] -crate-type = ["lib", "cdylib"] \ No newline at end of file +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol new file mode 100644 index 0000000000..732f68e492 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract FunctionCallsExample { + address private pythAddress; + bytes32 private priceId; + + struct Price { + int64 price; + uint64 conf; + int32 expo; + uint256 publishTime; // Using uint256 as Solidity does not have a native uint type + } + + struct PriceFeed { + bytes32 id; + Price price; + Price emaPrice; + } + + // Storage variables for the Price and PriceFeed data + Price private price; + PriceFeed private priceFeed; + + constructor(address _pythAddress, bytes32 _priceId) { + pythAddress = _pythAddress; + priceId = _priceId; + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index e3b02ef3cf..0da855235e 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -2,22 +2,41 @@ extern crate alloc; use alloc::vec::Vec; -use stylus_sdk::{ prelude::{entrypoint,public, sol_storage}, alloy_primitives:: Uint}; -use pyth_stylus::pyth::functions::get_price_no_older_than; +use alloy_primitives::Uint; +use stylus_sdk::prelude::{entrypoint,public, sol_storage}; +use pyth_stylus::pyth::{functions::{ + get_price_no_older_than, + get_ema_price_no_older_than, + get_ema_price_unsafe, + get_price_unsafe, + get_update_fee, + get_valid_time_period, + update_price_feeds, + update_price_feeds_if_necessary +},types::{ + StoragePrice, + StoragePriceFeed +}}; + sol_storage! { #[entrypoint] struct FunctionCallsExample { address pyth_address; bytes32 price_id; + StoragePrice price; + StoragePriceFeed price_feed; + StoragePrice ema_price; + StoragePriceFeed ema_price_feed; } } #[public] impl FunctionCallsExample { - pub fn check_price(&mut self) -> Result<(), Vec> { - let _price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; - Ok(()) + pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { + let price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; + self.price.set(price); + Ok(()) } } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs deleted file mode 100644 index 0f4b1a2eb1..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_call.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![cfg(feature = "e2e")] - -use alloy::hex; -use e2e::{receipt, send, watch, Account, EventExt, ReceiptExt, Revert}; -use eyre::Result; - -mod abi; - - -// ============================================================================ -// Integration Tests: Function Calls -// ============================================================================ - -#[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - assert_eq!(true, true); - - Ok(()) -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs new file mode 100644 index 0000000000..f693b3df89 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs @@ -0,0 +1,19 @@ +// #![cfg(feature = "e2e")] + +// // use alloy::hex; +// use e2e::{Account}; +// use eyre::Result; + +// // mod abi; + + +// // // ============================================================================ +// // // Integration Tests: Function Calls +// // // ============================================================================ + +// #[e2e::test] +// async fn constructs(alice: Account) -> Result<()> { +// assert_eq!(true, true); + +// Ok(()) +// } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs index 89256c96c1..8447502f6a 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs @@ -1,8 +1,7 @@ #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; -use alloc::vec::Vec; -use stylus_sdk::{ alloy_primitives:: Uint, console, prelude::{entrypoint,public, sol_storage}}; +use stylus_sdk::{ console, prelude::{entrypoint,public, sol_storage}}; use pyth_stylus::pyth::pyth_contract::PythContract; sol_storage! { diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 1801d6c3df..7e193cac7f 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -5,6 +5,26 @@ MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" cd .. +export RPC_URL=http://localhost:8547 +export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +export WALLER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +ls +cd "nitro-testnode" +./test-node.bash script send-l2 --to address_$WALLER_ADDRESS --ethamount 0.1 + +cd .. +cd "pyth-solidity" +deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') + +cd .. +# Output the captured address +echo "Mock Pyth Deployed to address: $deployed_to" +echo "MOCK_PYTH_ADDRESS=$deployed_to" > .env +echo "Deployed address saved to .env file." + NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index f7b9cdeb1c..fefc0b5a6a 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -1,14 +1,32 @@ -#!/bin/bash -set -e - MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" + +export RPC_URL=http://localhost:8547 +export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +export WALLER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + +cd .. +cd "nitro-testnode" +./test-node.bash script send-l2 --to address_$WALLER_ADDRESS --ethamount 0.1 + +cd .. +cd "pyth-solidity" +deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ + --rpc-url $RPC_URL \ + --private-key $PRIVATE_KEY \ + --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') + cd .. +# Output the captured address +echo "Mock Pyth Deployed to address: $deployed_to" +echo "MOCK_PYTH_ADDRESS=$deployed_to" > .env +echo "Deployed address saved to .env file." +# Run e2e tests NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" \ No newline at end of file From d1c010b3d60be6478d75a61e799459f9d746b091 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 29 Oct 2024 14:36:10 +0000 Subject: [PATCH 049/160] changes --- target_chains/ethereum/sdk/stylus/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 74fb8a029d..cdfff227e5 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -17,7 +17,7 @@ default-members = [ "lib/motsu-proc", "lib/e2e-proc", "examples/function-calls", - "examples/proxy-call-contracts" + "examples/proxy-call-contracts", ] # Explicitly set the resolver to version 2, which is the default for packages @@ -56,7 +56,6 @@ alloy = { version = "=0.1.4", features = [ "signer-local", "getrandom", ] } -getrandom = { version = "0.2.2", features = ["custom"] } # Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need # to keep both versions for compatibility with the Stylus SDK. Once they start # using `alloy` we can remove these. @@ -66,6 +65,7 @@ alloy-sol-macro = { version = "=0.7.6", default-features = false } alloy-sol-macro-expander = { version = "=0.7.6", default-features = false } alloy-sol-macro-input = { version = "=0.7.6", default-features = false } +dotenv = "0.15.0" const-hex = { version = "1.11.1", default-features = false } eyre = "0.6.8" keccak-const = "0.2.0" @@ -83,6 +83,7 @@ proc-macro2 = "1.0.79" quote = "1.0.35" # members +openzeppelin-crypto = { path = "lib/crypto" } motsu = { path = "lib/motsu"} motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } e2e = { path = "lib/e2e" } From 8826fa57f50fa21ad533ac1af3ab9baf1e6a4706 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:24:05 +0000 Subject: [PATCH 050/160] feture: Added Proxy call to bench --- .../ethereum/sdk/stylus/benches/src/lib.rs | 1 + .../ethereum/sdk/stylus/benches/src/main.rs | 7 +- .../sdk/stylus/benches/src/proxy_calls.rs | 105 ++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs index 2a2ecf73e0..4628241fa2 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs @@ -15,6 +15,7 @@ use serde::Deserialize; pub mod report; pub mod function_calls; +pub mod proxy_calls; #[derive(Debug, Deserialize)] struct ArbOtherFields { diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 4ca06a9c32..698d8d6913 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -1,5 +1,5 @@ use benches::{ - function_calls,report::BenchmarkReport, + function_calls,proxy_calls,report::BenchmarkReport, }; use futures::FutureExt; @@ -7,10 +7,7 @@ use futures::FutureExt; async fn main() -> eyre::Result<()> { let report = futures::future::try_join_all([ function_calls::bench().boxed(), - // // access_control::bench().boxed(), - // // erc20::bench().boxed(), - // // erc721::bench().boxed(), - // // merkle_proofs::bench().boxed(), + proxy_calls::bench().boxed(), ]) .await? .into_iter() diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs new file mode 100644 index 0000000000..283da86d37 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -0,0 +1,105 @@ +use std::str::FromStr; + +use alloy::{ + network::{AnyNetwork, EthereumWallet}, + primitives::{Address, FixedBytes as TypeFixedBytes, fixed_bytes}, + providers::ProviderBuilder, + sol, + sol_types::{sol_data::FixedBytes, SolCall, SolConstructor}, +}; +use e2e::{receipt, Account}; +use dotenv; + +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; + +sol!( + #[sol(rpc)] + contract ProxyCall{ + function getPriceNoOlderThan() external view; + } +); + +sol!("../examples/proxy-calls/src/constructor.sol"); + +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("ProxyCalls"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { + let alice = Account::new().await?; + let alice_addr = alice.address(); + let alice_wallet = ProviderBuilder::new() + .network::() + .with_recommended_fillers() + .wallet(EthereumWallet::from(alice.signer.clone())) + .on_http(alice.url().parse()?); + + let bob = Account::new().await?; + let bob_addr = bob.address(); + + let contract_addr = deploy(&alice, cache_opt).await?; + + let contract = ProxyCall::new(contract_addr, &alice_wallet); + println!("contract address: {contract_addr:?}"); + // let token_1 = uint!(1_U256); + // let token_2 = uint!(2_U256); + // let token_3 = uint!(3_U256); + // let token_4 = uint!(4_U256); + + let _ = receipt!(contract.getPriceNoOlderThan())?; + // let _ = receipt!(contract.mint(alice_addr, token_3))?; + // let _ = receipt!(contract.mint(alice_addr, token_4))?; + + // IMPORTANT: Order matters! + use ProxyCall::*; + //#[rustfmt::skip] + let receipts = vec![ + (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan())?), + // (approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_2))?), + // (getApprovedCall::SIGNATURE, receipt!(contract.getApproved(token_2))?), + // (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), + // (ownerOfCall::SIGNATURE, receipt!(contract.ownerOf(token_2))?), + // (safeTransferFromCall::SIGNATURE, receipt!(contract.safeTransferFrom(alice_addr, bob_addr, token_3))?), + // (setApprovalForAllCall::SIGNATURE, receipt!(contract.setApprovalForAll(bob_addr, true))?), + // (totalSupplyCall::SIGNATURE, receipt!(contract.totalSupply())?), + // (transferFromCall::SIGNATURE, receipt!(contract.transferFrom(alice_addr, bob_addr, token_4))?), + // (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_1))?), + // (burnCall::SIGNATURE, receipt!(contract.burn(token_1))?), + ]; + + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() +} + +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ + let pyth_addr = dotenv::var("MOCK_PYTH_ADDRESS")?; + let address = Address::from_str(&pyth_addr)?; + let id= "ETH"; + let mut bytes = [0u8; 32]; + bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); + let price_id :TypeFixedBytes<32> = TypeFixedBytes::from(bytes); + println!("pyth address: {address:?} price_id: {price_id:?}"); + let args = ProxyCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; + let args = alloy::hex::encode(args.abi_encode()); + crate::deploy(account, "proxy_calls", Some(args), cache_opt).await +} From 343de1333dab8f4f614f516f8a8975523ed0dd0b Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:24:45 +0000 Subject: [PATCH 051/160] chore: Removed sol file --- .../pyth-sdk-solidity/AbstractPyth.sol | 138 ---- .../@pythnetwork/pyth-sdk-solidity/IPyth.sol | 141 ---- .../pyth-sdk-solidity/IPythEvents.sol | 18 - .../@pythnetwork/pyth-sdk-solidity/LICENSE | 13 - .../pyth-sdk-solidity/MockPyth.sol | 189 ----- .../pyth-sdk-solidity/PythAggregatorV3.sol | 117 --- .../pyth-sdk-solidity/PythErrors.sol | 48 -- .../pyth-sdk-solidity/PythStructs.sol | 33 - .../pyth-sdk-solidity/PythUtils.sol | 34 - .../@pythnetwork/pyth-sdk-solidity/README.md | 103 --- .../pyth-sdk-solidity/abis/AbstractPyth.json | 661 ---------------- .../pyth-sdk-solidity/abis/IPyth.json | 452 ----------- .../pyth-sdk-solidity/abis/IPythEvents.json | 33 - .../pyth-sdk-solidity/abis/MockPyth.json | 746 ------------------ .../pyth-sdk-solidity/abis/PythErrors.json | 72 -- .../pyth-sdk-solidity/abis/PythUtils.json | 31 - .../pyth-sdk-solidity/package.json | 34 - .../examples/function-calls/src/MockPyth.sol | 44 -- 18 files changed, 2907 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol deleted file mode 100644 index 691b4804c3..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./PythStructs.sol"; -import "./IPyth.sol"; -import "./PythErrors.sol"; - -abstract contract AbstractPyth is IPyth { - /// @notice Returns the price feed with given id. - /// @dev Reverts if the price does not exist. - /// @param id The Pyth Price Feed ID of which to fetch the PriceFeed. - function queryPriceFeed( - bytes32 id - ) public view virtual returns (PythStructs.PriceFeed memory priceFeed); - - /// @notice Returns true if a price feed with the given id exists. - /// @param id The Pyth Price Feed ID of which to check its existence. - function priceFeedExists( - bytes32 id - ) public view virtual returns (bool exists); - - /// @notice This function is deprecated and is only kept for backward compatibility. - function getValidTimePeriod() - public - view - virtual - returns (uint validTimePeriod); - - /// @notice This function is deprecated and is only kept for backward compatibility. - function getPrice( - bytes32 id - ) external view virtual returns (PythStructs.Price memory price) { - return getPriceNoOlderThan(id, getValidTimePeriod()); - } - - /// @notice This function is deprecated and is only kept for backward compatibility. - function getEmaPrice( - bytes32 id - ) external view virtual returns (PythStructs.Price memory price) { - return getEmaPriceNoOlderThan(id, getValidTimePeriod()); - } - - function getPriceUnsafe( - bytes32 id - ) public view virtual override returns (PythStructs.Price memory price) { - PythStructs.PriceFeed memory priceFeed = queryPriceFeed(id); - return priceFeed.price; - } - - function getPriceNoOlderThan( - bytes32 id, - uint age - ) public view virtual override returns (PythStructs.Price memory price) { - price = getPriceUnsafe(id); - - if (diff(block.timestamp, price.publishTime) > age) - revert PythErrors.StalePrice(); - - return price; - } - - function getEmaPriceUnsafe( - bytes32 id - ) public view virtual override returns (PythStructs.Price memory price) { - PythStructs.PriceFeed memory priceFeed = queryPriceFeed(id); - return priceFeed.emaPrice; - } - - function getEmaPriceNoOlderThan( - bytes32 id, - uint age - ) public view virtual override returns (PythStructs.Price memory price) { - price = getEmaPriceUnsafe(id); - - if (diff(block.timestamp, price.publishTime) > age) - revert PythErrors.StalePrice(); - - return price; - } - - function diff(uint x, uint y) internal pure returns (uint) { - if (x > y) { - return x - y; - } else { - return y - x; - } - } - - // Access modifier is overridden to public to be able to call it locally. - function updatePriceFeeds( - bytes[] calldata updateData - ) public payable virtual override; - - function updatePriceFeedsIfNecessary( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64[] calldata publishTimes - ) external payable virtual override { - if (priceIds.length != publishTimes.length) - revert PythErrors.InvalidArgument(); - - for (uint i = 0; i < priceIds.length; i++) { - if ( - !priceFeedExists(priceIds[i]) || - queryPriceFeed(priceIds[i]).price.publishTime < publishTimes[i] - ) { - updatePriceFeeds(updateData); - return; - } - } - - revert PythErrors.NoFreshUpdate(); - } - - function parsePriceFeedUpdates( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - virtual - override - returns (PythStructs.PriceFeed[] memory priceFeeds); - - function parsePriceFeedUpdatesUnique( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - virtual - override - returns (PythStructs.PriceFeed[] memory priceFeeds); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol deleted file mode 100644 index c94e8c3ed8..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPyth.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./PythStructs.sol"; -import "./IPythEvents.sol"; - -/// @title Consume prices from the Pyth Network (https://pyth.network/). -/// @dev Please refer to the guidance at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how to consume prices safely. -/// @author Pyth Data Association -interface IPyth is IPythEvents { - /// @notice Returns the price of a price feed without any sanity checks. - /// @dev This function returns the most recent price update in this contract without any recency checks. - /// This function is unsafe as the returned price update may be arbitrarily far in the past. - /// - /// Users of this function should check the `publishTime` in the price to ensure that the returned price is - /// sufficiently recent for their application. If you are considering using this function, it may be - /// safer / easier to use `getPriceNoOlderThan`. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. - function getPriceUnsafe( - bytes32 id - ) external view returns (PythStructs.Price memory price); - - /// @notice Returns the price that is no older than `age` seconds of the current time. - /// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in - /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently - /// recently. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. - function getPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (PythStructs.Price memory price); - - /// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks. - /// @dev This function returns the same price as `getEmaPrice` in the case where the price is available. - /// However, if the price is not recent this function returns the latest available price. - /// - /// The returned price can be from arbitrarily far in the past; this function makes no guarantees that - /// the returned price is recent or useful for any particular application. - /// - /// Users of this function should check the `publishTime` in the price to ensure that the returned price is - /// sufficiently recent for their application. If you are considering using this function, it may be - /// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. - function getEmaPriceUnsafe( - bytes32 id - ) external view returns (PythStructs.Price memory price); - - /// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds - /// of the current time. - /// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in - /// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently - /// recently. - /// @return price - please read the documentation of PythStructs.Price to understand how to use this safely. - function getEmaPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (PythStructs.Price memory price); - - /// @notice Update price feeds with given update messages. - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// Prices will be updated if they are more recent than the current stored prices. - /// The call will succeed even if the update is not the most recent. - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid. - /// @param updateData Array of price update data. - function updatePriceFeeds(bytes[] calldata updateData) external payable; - - /// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is - /// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the - /// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`. - /// - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// - /// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime - /// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have - /// a newer or equal publish time than the given publish time, it will reject the transaction to save gas. - /// Otherwise, it calls updatePriceFeeds method to update the prices. - /// - /// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]` - function updatePriceFeedsIfNecessary( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64[] calldata publishTimes - ) external payable; - - /// @notice Returns the required fee to update an array of price updates. - /// @param updateData Array of price update data. - /// @return feeAmount The required fee in Wei. - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount); - - /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published - /// within `minPublishTime` and `maxPublishTime`. - /// - /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; - /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they - /// are more recent than the current stored prices. - /// - /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling - /// `getUpdateFee` with the length of the `updateData` array. - /// - /// - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is - /// no update for any of the given `priceIds` within the given time range. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). - function parsePriceFeedUpdates( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); - - /// @notice Similar to `parsePriceFeedUpdates` but ensures the updates returned are - /// the first updates published in minPublishTime. That is, if there are multiple updates for a given timestamp, - /// this method will return the first update. This method may store the price updates on-chain, if they - /// are more recent than the current stored prices. - /// - /// - /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is - /// no update for any of the given `priceIds` within the given time range and uniqueness condition. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). - function parsePriceFeedUpdatesUnique( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol deleted file mode 100644 index a293776162..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/IPythEvents.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -/// @title IPythEvents contains the events that Pyth contract emits. -/// @dev This interface can be used for listening to the updates for off-chain and testing purposes. -interface IPythEvents { - /// @dev Emitted when the price feed with `id` has received a fresh update. - /// @param id The Pyth Price Feed ID. - /// @param publishTime Publish time of the given price update. - /// @param price Price of the given price update. - /// @param conf Confidence interval of the given price update. - event PriceFeedUpdate( - bytes32 indexed id, - uint64 publishTime, - int64 price, - uint64 conf - ); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE deleted file mode 100644 index 92f6c4365f..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2024 Pyth Data Association. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol deleted file mode 100644 index dcb8d7864a..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/MockPyth.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./AbstractPyth.sol"; -import "./PythStructs.sol"; -import "./PythErrors.sol"; - -contract MockPyth is AbstractPyth { - mapping(bytes32 => PythStructs.PriceFeed) priceFeeds; - - uint singleUpdateFeeInWei; - uint validTimePeriod; - - constructor(uint _validTimePeriod, uint _singleUpdateFeeInWei) { - singleUpdateFeeInWei = _singleUpdateFeeInWei; - validTimePeriod = _validTimePeriod; - } - - function queryPriceFeed( - bytes32 id - ) public view override returns (PythStructs.PriceFeed memory priceFeed) { - if (priceFeeds[id].id == 0) revert PythErrors.PriceFeedNotFound(); - return priceFeeds[id]; - } - - function priceFeedExists(bytes32 id) public view override returns (bool) { - return (priceFeeds[id].id != 0); - } - - function getValidTimePeriod() public view override returns (uint) { - return validTimePeriod; - } - - // Takes an array of encoded price feeds and stores them. - // You can create this data either by calling createPriceFeedUpdateData or - // by using web3.js or ethers abi utilities. - // @note: The updateData expected here is different from the one used in the main contract. - // In particular, the expected format is: - // [ - // abi.encode( - // PythStructs.PriceFeed( - // bytes32 id, - // PythStructs.Price price, - // PythStructs.Price emaPrice - // ), - // uint64 prevPublishTime - // ) - // ] - function updatePriceFeeds( - bytes[] calldata updateData - ) public payable override { - uint requiredFee = getUpdateFee(updateData); - if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - - for (uint i = 0; i < updateData.length; i++) { - PythStructs.PriceFeed memory priceFeed = abi.decode( - updateData[i], - (PythStructs.PriceFeed) - ); - - uint lastPublishTime = priceFeeds[priceFeed.id].price.publishTime; - - if (lastPublishTime < priceFeed.price.publishTime) { - // Price information is more recent than the existing price information. - priceFeeds[priceFeed.id] = priceFeed; - emit PriceFeedUpdate( - priceFeed.id, - uint64(priceFeed.price.publishTime), - priceFeed.price.price, - priceFeed.price.conf - ); - } - } - } - - function getUpdateFee( - bytes[] calldata updateData - ) public view override returns (uint feeAmount) { - return singleUpdateFeeInWei * updateData.length; - } - - function parsePriceFeedUpdatesInternal( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime, - bool unique - ) internal returns (PythStructs.PriceFeed[] memory feeds) { - uint requiredFee = getUpdateFee(updateData); - if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - - feeds = new PythStructs.PriceFeed[](priceIds.length); - - for (uint i = 0; i < priceIds.length; i++) { - for (uint j = 0; j < updateData.length; j++) { - uint64 prevPublishTime; - (feeds[i], prevPublishTime) = abi.decode( - updateData[j], - (PythStructs.PriceFeed, uint64) - ); - - uint publishTime = feeds[i].price.publishTime; - if (priceFeeds[feeds[i].id].price.publishTime < publishTime) { - priceFeeds[feeds[i].id] = feeds[i]; - emit PriceFeedUpdate( - feeds[i].id, - uint64(publishTime), - feeds[i].price.price, - feeds[i].price.conf - ); - } - - if (feeds[i].id == priceIds[i]) { - if ( - minPublishTime <= publishTime && - publishTime <= maxPublishTime && - (!unique || prevPublishTime < minPublishTime) - ) { - break; - } else { - feeds[i].id = 0; - } - } - } - - if (feeds[i].id != priceIds[i]) - revert PythErrors.PriceFeedNotFoundWithinRange(); - } - } - - function parsePriceFeedUpdates( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - return - parsePriceFeedUpdatesInternal( - updateData, - priceIds, - minPublishTime, - maxPublishTime, - false - ); - } - - function parsePriceFeedUpdatesUnique( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - return - parsePriceFeedUpdatesInternal( - updateData, - priceIds, - minPublishTime, - maxPublishTime, - true - ); - } - - function createPriceFeedUpdateData( - bytes32 id, - int64 price, - uint64 conf, - int32 expo, - int64 emaPrice, - uint64 emaConf, - uint64 publishTime, - uint64 prevPublishTime - ) public pure returns (bytes memory priceFeedData) { - PythStructs.PriceFeed memory priceFeed; - - priceFeed.id = id; - - priceFeed.price.price = price; - priceFeed.price.conf = conf; - priceFeed.price.expo = expo; - priceFeed.price.publishTime = publishTime; - - priceFeed.emaPrice.price = emaPrice; - priceFeed.emaPrice.conf = emaConf; - priceFeed.emaPrice.expo = expo; - priceFeed.emaPrice.publishTime = publishTime; - - priceFeedData = abi.encode(priceFeed, prevPublishTime); - } -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol deleted file mode 100644 index 1d8ba4509c..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythAggregatorV3.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.0; - -import {PythStructs} from "./PythStructs.sol"; -import {IPyth} from "./IPyth.sol"; - -// This interface is forked from the Zerolend Adapter found here: -// https://github.com/zerolend/pyth-oracles/blob/master/contracts/PythAggregatorV3.sol -// Original license found under licenses/zerolend-pyth-oracles.md - -/** - * @title A port of the ChainlinkAggregatorV3 interface that supports Pyth price feeds - * @notice This does not store any roundId information on-chain. Please review the code before using this implementation. - * Users should deploy an instance of this contract to wrap every price feed id that they need to use. - */ -contract PythAggregatorV3 { - bytes32 public priceId; - IPyth public pyth; - - constructor(address _pyth, bytes32 _priceId) { - priceId = _priceId; - pyth = IPyth(_pyth); - } - - // Wrapper function to update the underlying Pyth price feeds. Not part of the AggregatorV3 interface but useful. - function updateFeeds(bytes[] calldata priceUpdateData) public payable { - // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data - // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. - // See section "How Pyth Works on EVM Chains" below for more information. - uint fee = pyth.getUpdateFee(priceUpdateData); - pyth.updatePriceFeeds{value: fee}(priceUpdateData); - - // refund remaining eth - payable(msg.sender).call{value: address(this).balance}(""); - } - - function decimals() public view virtual returns (uint8) { - PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); - return uint8(-1 * int8(price.expo)); - } - - function description() public pure returns (string memory) { - return "A port of a chainlink aggregator powered by pyth network feeds"; - } - - function version() public pure returns (uint256) { - return 1; - } - - function latestAnswer() public view virtual returns (int256) { - PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); - return int256(price.price); - } - - function latestTimestamp() public view returns (uint256) { - PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); - return price.publishTime; - } - - function latestRound() public view returns (uint256) { - // use timestamp as the round id - return latestTimestamp(); - } - - function getAnswer(uint256) public view returns (int256) { - return latestAnswer(); - } - - function getTimestamp(uint256) external view returns (uint256) { - return latestTimestamp(); - } - - function getRoundData( - uint80 _roundId - ) - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); - return ( - _roundId, - int256(price.price), - price.publishTime, - price.publishTime, - _roundId - ); - } - - function latestRoundData() - external - view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) - { - PythStructs.Price memory price = pyth.getPriceUnsafe(priceId); - roundId = uint80(price.publishTime); - return ( - roundId, - int256(price.price), - price.publishTime, - price.publishTime, - roundId - ); - } -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol deleted file mode 100644 index 2b5457740e..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythErrors.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Apache 2 - -pragma solidity ^0.8.0; - -library PythErrors { - // Function arguments are invalid (e.g., the arguments lengths mismatch) - // Signature: 0xa9cb9e0d - error InvalidArgument(); - // Update data is coming from an invalid data source. - // Signature: 0xe60dce71 - error InvalidUpdateDataSource(); - // Update data is invalid (e.g., deserialization error) - // Signature: 0xe69ffece - error InvalidUpdateData(); - // Insufficient fee is paid to the method. - // Signature: 0x025dbdd4 - error InsufficientFee(); - // There is no fresh update, whereas expected fresh updates. - // Signature: 0xde2c57fa - error NoFreshUpdate(); - // There is no price feed found within the given range or it does not exists. - // Signature: 0x45805f5d - error PriceFeedNotFoundWithinRange(); - // Price feed not found or it is not pushed on-chain yet. - // Signature: 0x14aebe68 - error PriceFeedNotFound(); - // Requested price is stale. - // Signature: 0x19abf40e - error StalePrice(); - // Given message is not a valid Wormhole VAA. - // Signature: 0x2acbe915 - error InvalidWormholeVaa(); - // Governance message is invalid (e.g., deserialization error). - // Signature: 0x97363b35 - error InvalidGovernanceMessage(); - // Governance message is not for this contract. - // Signature: 0x63daeb77 - error InvalidGovernanceTarget(); - // Governance message is coming from an invalid data source. - // Signature: 0x360f2d87 - error InvalidGovernanceDataSource(); - // Governance message is old. - // Signature: 0x88d1b847 - error OldGovernanceMessage(); - // The wormhole address to set in SetWormholeAddress governance is invalid. - // Signature: 0x13d3ed82 - error InvalidWormholeAddressToSet(); -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol deleted file mode 100644 index b3d2ee2c6a..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythStructs.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -contract PythStructs { - // A price with a degree of uncertainty, represented as a price +- a confidence interval. - // - // The confidence interval roughly corresponds to the standard error of a normal distribution. - // Both the price and confidence are stored in a fixed-point numeric representation, - // `x * (10^expo)`, where `expo` is the exponent. - // - // Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how - // to how this price safely. - struct Price { - // Price - int64 price; - // Confidence interval around the price - uint64 conf; - // Price exponent - int32 expo; - // Unix timestamp describing when the price was published - uint publishTime; - } - - // PriceFeed represents a current aggregate price from pyth publisher feeds. - struct PriceFeed { - // The price ID. - bytes32 id; - // Latest available price - Price price; - // Latest available exponentially-weighted moving average price - Price emaPrice; - } -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol deleted file mode 100644 index 04b7f51f34..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/PythUtils.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -library PythUtils { - /// @notice Converts a Pyth price to a uint256 with a target number of decimals - /// @param price The Pyth price - /// @param expo The Pyth price exponent - /// @param targetDecimals The target number of decimals - /// @return The price as a uint256 - /// @dev Function will lose precision if targetDecimals is less than the Pyth price decimals. - /// This method will truncate any digits that cannot be represented by the targetDecimals. - /// e.g. If the price is 0.000123 and the targetDecimals is 2, the result will be 0 - function convertToUint( - int64 price, - int32 expo, - uint8 targetDecimals - ) public pure returns (uint256) { - if (price < 0 || expo > 0 || expo < -255) { - revert(); - } - - uint8 priceDecimals = uint8(uint32(-1 * expo)); - - if (targetDecimals >= priceDecimals) { - return - uint(uint64(price)) * - 10 ** uint32(targetDecimals - priceDecimals); - } else { - return - uint(uint64(price)) / - 10 ** uint32(priceDecimals - targetDecimals); - } - } -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md deleted file mode 100644 index dc50e8a482..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Pyth Solidity SDK - -This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle using Solidity. Also, it contains [the Pyth Interface ABI](./abis/IPyth.json) that you can use in your libraries -to communicate with the Pyth contract. - -It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming Pyth data. - -## Installation - -###Truffle/Hardhat - -If you are using Truffle or Hardhat, simply install the NPM package: - -```bash -npm install @pythnetwork/pyth-sdk-solidity -``` - -###Foundry - -If you are using Foundry, you will need to create an NPM project if you don't already have one. -From the root directory of your project, run: - -```bash -npm init -y -npm install @pythnetwork/pyth-sdk-solidity -``` - -Then add the following line to your `remappings.txt` file: - -```text -@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity -``` - -## Example Usage - -To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this -interface in order to use the prices safely. - -For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed -you're interested in. The price feeds available on each chain are listed [below](#target-chains). - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; -import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; - -contract ExampleContract { - IPyth pyth; - - constructor(address pythContract) { - pyth = IPyth(pythContract); - } - - function getBtcUsdPrice( - bytes[] calldata priceUpdateData - ) public payable returns (PythStructs.Price memory) { - // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data - // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. - // See section "How Pyth Works on EVM Chains" below for more information. - uint fee = pyth.getUpdateFee(priceUpdateData); - pyth.updatePriceFeeds{ value: fee }(priceUpdateData); - - bytes32 priceID = 0xf9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b; - // Read the current value of priceID, aborting the transaction if the price has not been updated in the last 10 - // seconds. - return pyth.getPriceNoOlderThan(priceID, 10); - } -} - -``` - -## How Pyth Works on EVM Chains - -Pyth prices are published on Pythnet, and relayed to EVM chains using the [Wormhole Network](https://wormholenetwork.com/) as a cross-chain message passing bridge. The Wormhole Network observes when Pyth prices on Pythnet have changed and publishes an off-chain signed message attesting to this fact. This is explained in more detail [here](https://docs.wormholenetwork.com/wormhole/). - -This signed message can then be submitted to the Pyth contract on the EVM networks along the required update fee for it, which will verify the Wormhole message and update the Pyth contract with the new price. - -Please refer to [Pyth On-Demand Updates page](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand) for more information. - -## Solidity Target Chains - -[This](https://docs.pyth.network/documentation/pythnet-price-feeds/evm#networks) document contains list of the EVM networks that Pyth is available on. - -You can find a list of available price feeds [here](https://pyth.network/developers/price-feed-ids/). - -## Mocking Pyth - -[MockPyth](./MockPyth.sol) is a mock contract that you can use and deploy locally to mock Pyth contract behaviour. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds (the struct defined in [PythStructs](./PythStructs.sol)) as its argument. You can create encoded price feeds either by using web3.js or ethers ABI utilities or calling `createPriceFeedUpdateData` function in the mock contract. - -## Development - -### ABIs - -When making changes to a contract interface, please make sure to update the ABI files too. You can update it using `npm run generate-abi` and it will update the ABI files in [abis](./abis) directory. If you create a new contract, you also need to add the contract name in [the ABI generation script](./scripts/generateAbi.js#L5) so the script can create the ABI file for the new contract as well. - -### Releases - -We use [Semantic Versioning](https://semver.org/) for our releases. In order to release a new version of this package and publish it to npm, follow these steps: - -1. Run `npm version --no-git-tag-version`. This command will update the version of the package. Then push your changes to github. -2. Once your change is merged into `main`, create a release with tag `v` like `v1.5.2`, and a github action will automatically publish the new version of this package to npm. diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json deleted file mode 100644 index 7b6724065f..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json +++ /dev/null @@ -1,661 +0,0 @@ -[ - { - "inputs": [], - "name": "InvalidArgument", - "type": "error" - }, - { - "inputs": [], - "name": "NoFreshUpdate", - "type": "error" - }, - { - "inputs": [], - "name": "StalePrice", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "publishTime", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "conf", - "type": "uint64" - } - ], - "name": "PriceFeedUpdate", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getEmaPrice", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getEmaPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getEmaPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getPrice", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "getUpdateFee", - "outputs": [ - { - "internalType": "uint256", - "name": "feeAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getValidTimePeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "validTimePeriod", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdates", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "priceFeeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdatesUnique", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "priceFeeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "priceFeedExists", - "outputs": [ - { - "internalType": "bool", - "name": "exists", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "queryPriceFeed", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed", - "name": "priceFeed", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "updatePriceFeeds", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64[]", - "name": "publishTimes", - "type": "uint64[]" - } - ], - "name": "updatePriceFeedsIfNecessary", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json deleted file mode 100644 index 0b5fa851b2..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPyth.json +++ /dev/null @@ -1,452 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "publishTime", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "conf", - "type": "uint64" - } - ], - "name": "PriceFeedUpdate", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getEmaPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getEmaPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "getUpdateFee", - "outputs": [ - { - "internalType": "uint256", - "name": "feeAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdates", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "priceFeeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdatesUnique", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "priceFeeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "updatePriceFeeds", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64[]", - "name": "publishTimes", - "type": "uint64[]" - } - ], - "name": "updatePriceFeedsIfNecessary", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json deleted file mode 100644 index 1a89732089..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/IPythEvents.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "publishTime", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "conf", - "type": "uint64" - } - ], - "name": "PriceFeedUpdate", - "type": "event" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json deleted file mode 100644 index a917b56a28..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/MockPyth.json +++ /dev/null @@ -1,746 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "uint256", - "name": "_validTimePeriod", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_singleUpdateFeeInWei", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "InsufficientFee", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidArgument", - "type": "error" - }, - { - "inputs": [], - "name": "NoFreshUpdate", - "type": "error" - }, - { - "inputs": [], - "name": "PriceFeedNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "PriceFeedNotFoundWithinRange", - "type": "error" - }, - { - "inputs": [], - "name": "StalePrice", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "publishTime", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "conf", - "type": "uint64" - } - ], - "name": "PriceFeedUpdate", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "int64", - "name": "emaPrice", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "emaConf", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "publishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "prevPublishTime", - "type": "uint64" - } - ], - "name": "createPriceFeedUpdateData", - "outputs": [ - { - "internalType": "bytes", - "name": "priceFeedData", - "type": "bytes" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getEmaPrice", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getEmaPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getEmaPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getPrice", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "age", - "type": "uint256" - } - ], - "name": "getPriceNoOlderThan", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "getPriceUnsafe", - "outputs": [ - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "getUpdateFee", - "outputs": [ - { - "internalType": "uint256", - "name": "feeAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getValidTimePeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdates", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "feeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdatesUnique", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "feeds", - "type": "tuple[]" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "priceFeedExists", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "queryPriceFeed", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed", - "name": "priceFeed", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - } - ], - "name": "updatePriceFeeds", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64[]", - "name": "publishTimes", - "type": "uint64[]" - } - ], - "name": "updatePriceFeedsIfNecessary", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json deleted file mode 100644 index f8fdc192ce..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythErrors.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "inputs": [], - "name": "InsufficientFee", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidArgument", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidGovernanceDataSource", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidGovernanceMessage", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidGovernanceTarget", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidUpdateData", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidUpdateDataSource", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidWormholeAddressToSet", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidWormholeVaa", - "type": "error" - }, - { - "inputs": [], - "name": "NoFreshUpdate", - "type": "error" - }, - { - "inputs": [], - "name": "OldGovernanceMessage", - "type": "error" - }, - { - "inputs": [], - "name": "PriceFeedNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "PriceFeedNotFoundWithinRange", - "type": "error" - }, - { - "inputs": [], - "name": "StalePrice", - "type": "error" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json deleted file mode 100644 index c30f138950..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/abis/PythUtils.json +++ /dev/null @@ -1,31 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint8", - "name": "targetDecimals", - "type": "uint8" - } - ], - "name": "convertToUint", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - } -] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json deleted file mode 100644 index 9622fc6320..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/@pythnetwork/pyth-sdk-solidity/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@pythnetwork/pyth-sdk-solidity", - "version": "4.0.0", - "description": "Read prices from the Pyth oracle", - "repository": { - "type": "git", - "url": "https://github.com/pyth-network/pyth-crosschain", - "directory": "target_chains/ethereum/sdk/solidity" - }, - "scripts": { - "format": "prettier --write .", - "generate-abi": "generate-abis IPyth IPythEvents AbstractPyth MockPyth PythErrors PythUtils", - "check-abi": "git diff --exit-code abis", - "build": "solcjs --bin MockPyth.sol --base-path . -o build/" - }, - "keywords": [ - "pyth", - "solidity", - "oracle" - ], - "author": "Pyth Data Association", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/pyth-network/pyth-crosschain/issues" - }, - "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/sdk/solidity", - "devDependencies": { - "abi_generator": "0.0.0", - "prettier": "^2.7.1", - "prettier-plugin-solidity": "^1.0.0-rc.1", - "solc": "^0.8.25" - }, - "gitHead": "a36f7ac5cc00a9c3c96beee509b58471ff5b9c40" -} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol deleted file mode 100644 index ef5bed8460..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/MockPyth.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "./@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; - -contract MockPythSample is MockPyth { - uint randNonce = 0; - string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; - bytes32[] priceIds = new bytes32[](tickers.length); - - constructor( - uint validTimePeriod, uint singleUpdateFeeInWei - ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { - getPriceIds(); - for (uint i = 0; i< tickers.length; i++) { - uint randomNumber = randNumber(priceIds[i],10); - int64 price = int64(uint64(randomNumber + randNumber(priceIds[i],2))); - uint64 conf = uint64(randomNumber+ randNumber(priceIds[i],3)); - int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40))); - int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5))); - uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6)); - createPriceFeedUpdateData(priceIds[i],price, conf, expo,emaPrice, emaConf, uint64(block.timestamp),0); - } - } - - function getPriceIds() internal { - for (uint i = 0; i < tickers.length; i++) { - bytes memory tempEmptyStringTest = bytes(tickers[i]); - if (tempEmptyStringTest.length == 0) { - priceIds[i] = bytes32(0); - } - else { - priceIds[i] = keccak256(abi.encodePacked(tickers[i])); - } - } - } - - function randNumber(bytes32 ticker, uint salt) internal returns(uint) - { - // increase nonce - randNonce++; - return uint(keccak256(abi.encodePacked(block.timestamp, ticker,msg.sender,randNonce, salt))) % randNonce; - } -} \ No newline at end of file From 1407ed4f1085d59e4dcb46690bbe57324af67889 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:25:09 +0000 Subject: [PATCH 052/160] Added deploy scripts --- .../ethereum/sdk/stylus/scripts/deploy.sh | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100755 target_chains/ethereum/sdk/stylus/scripts/deploy.sh diff --git a/target_chains/ethereum/sdk/stylus/scripts/deploy.sh b/target_chains/ethereum/sdk/stylus/scripts/deploy.sh new file mode 100755 index 0000000000..65b77e6efc --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/scripts/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +mydir=$(dirname "$0") +cd "$mydir" || exit +cd .. + +export RPC_URL=http://localhost:8547 +export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +export WALLER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + +cd "nitro-testnode" +./test-node.bash script send-l2 --to address_$WALLER_ADDRESS --ethamount 0.1 + + +cd .. +# Check contract wasm binary by crate name +deploy_wasm () { + local CONTRACT_CRATE_NAME=$1 + local CONTRACT_BIN_NAME="${CONTRACT_CRATE_NAME//-/_}.wasm" + + echo "deploy contract $CONTRACT_CRATE_NAME" + cargo stylus deploy --wasm-file ./target/wasm32-unknown-unknown/release/"$CONTRACT_BIN_NAME" \ + --endpoint $RPC_URL \ + --private-key $PRIVATE_KEY \ + --no-verify + } + +# Retrieve all alphanumeric contract's crate names in `./examples` directory. +get_example_crate_names () { + # shellcheck disable=SC2038 + # NOTE: optimistically relying on the 'name = ' string at Cargo.toml file + find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs grep 'name = ' | grep -oE '".*"' | tr -d "'\"" +} + +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} + +cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort + +for CRATE_NAME in $(get_example_crate_names) +do + deploy_wasm "$CRATE_NAME" +done From 15a47a9118ef67b635753d7297b7d90b3c6a28b2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:25:43 +0000 Subject: [PATCH 053/160] chore: proxy calls --- .../stylus/examples/proxy-calls/Cargo.toml | 25 ++++++++ .../examples/proxy-calls/src/constructor.sol | 29 +++++++++ .../stylus/examples/proxy-calls/src/lib.rs | 26 ++++++++ .../examples/proxy-calls/tests/abi/mod.rs | 64 +++++++++++++++++++ .../examples/proxy-calls/tests/proxy-calls.rs | 20 ++++++ 5 files changed, 164 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs create mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml new file mode 100644 index 0000000000..5785f25f60 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "proxy-calls-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +pyth-stylus.workspace = true +alloy-primitives.workspace = true +stylus-sdk.workspace = true +mini-alloc.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[features] +e2e = [] \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol new file mode 100644 index 0000000000..61a26c4d19 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ProxyCallsExample { + address private pythAddress; + bytes32 private priceId; + + struct Price { + int64 price; + uint64 conf; + int32 expo; + uint256 publishTime; // Using uint256 as Solidity does not have a native uint type + } + + struct PriceFeed { + bytes32 id; + Price price; + Price emaPrice; + } + + // Storage variables for the Price and PriceFeed data + Price private price; + PriceFeed private priceFeed; + + constructor(address _pythAddress, bytes32 _priceId) { + pythAddress = _pythAddress; + priceId = _priceId; + } +} diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs new file mode 100644 index 0000000000..ac2224dc0c --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs @@ -0,0 +1,26 @@ +#![cfg_attr(not(test), no_std, no_main)] +extern crate alloc; + +use alloc::vec::Vec; +use alloy_primitives::Uint; +use stylus_sdk::prelude::{entrypoint,public, sol_storage}; +use pyth_stylus::pyth::pyth_contract::PythContract; + + +sol_storage! { + #[entrypoint] + struct ProxyCallsExample { + address pyth_address; + bytes32 price_id; + } +} + + +#[public] +impl ProxyCallsExample { + pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { + // let price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; + // self.price.set(price); + Ok(()) + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs new file mode 100644 index 0000000000..34ec44e34a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs @@ -0,0 +1,64 @@ +#![allow(dead_code)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Pyth { + function approve(address to, uint256 tokenId) external; + #[derive(Debug)] + function balanceOf(address owner) external view returns (uint256 balance); + #[derive(Debug)] + function getApproved(uint256 tokenId) external view returns (address approved); + #[derive(Debug)] + function isApprovedForAll(address owner, address operator) external view returns (bool approved); + #[derive(Debug)] + function ownerOf(uint256 tokenId) external view returns (address ownerOf); + function safeTransferFrom(address from, address to, uint256 tokenId) external; + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + function setApprovalForAll(address operator, bool approved) external; + function totalSupply() external view returns (uint256 totalSupply); + function transferFrom(address from, address to, uint256 tokenId) external; + + function mint(address to, uint256 tokenId) external; + function burn(uint256 tokenId) external; + + function paused() external view returns (bool paused); + function pause() external; + function unpause() external; + #[derive(Debug)] + function whenPaused() external view; + #[derive(Debug)] + function whenNotPaused() external view; + + #[derive(Debug)] + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + #[derive(Debug)] + function tokenByIndex(uint256 index) external view returns (uint256 tokenId); + + function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); + + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + error ERC721InsufficientApproval(address operator, uint256 tokenId); + error ERC721InvalidApprover(address approver); + error ERC721InvalidOperator(address operator); + error ERC721InvalidOwner(address owner); + error ERC721InvalidReceiver(address receiver); + error ERC721InvalidSender(address sender); + error ERC721NonexistentToken(uint256 tokenId); + error ERC721OutOfBoundsIndex(address owner, uint256 index); + error ERC721EnumerableForbiddenBatchMint(); + error EnforcedPause(); + error ExpectedPause(); + + #[derive(Debug, PartialEq)] + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + #[derive(Debug, PartialEq)] + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + #[derive(Debug, PartialEq)] + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + #[derive(Debug, PartialEq)] + event Paused(address account); + #[derive(Debug, PartialEq)] + event Unpaused(address account); + } +); \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs new file mode 100644 index 0000000000..6380303eb0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs @@ -0,0 +1,20 @@ +#![cfg(feature = "e2e")] + +use alloy::hex; +use e2e::{receipt,ReceiptExt,Account}; +use eyre::Result; + +mod abi; + + +// // ============================================================================ +// // Integration Tests: Proxy Calls +// // ============================================================================ + + #[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; +// // assert_eq!(true, true); + + Ok(()) + } From 5904a1a3d3ce2f37c9fc230381a36cbe467d54de Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:26:04 +0000 Subject: [PATCH 054/160] chore: removed file --- .../examples/proxy-call-contracts/Cargo.toml | 23 ---------------- .../examples/proxy-call-contracts/src/lib.rs | 27 ------------------- 2 files changed, 50 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml deleted file mode 100644 index 7519ee9b1f..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "proxy-call-contracts" -authors.workspace = true -license.workspace = true -edition.workspace = true -repository.workspace = true -publish = false -version = "0.0.0" - -[dependencies] -alloy-primitives.workspace = true -stylus-sdk.workspace = true -mini-alloc.workspace = true -pyth-stylus.workspace = true - -[dev-dependencies] -alloy.workspace = true -eyre.workspace = true -tokio.workspace = true -e2e.workspace = true - -[features] -e2e = [] diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs deleted file mode 100644 index 8447502f6a..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-call-contracts/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![cfg_attr(not(test), no_std, no_main)] -extern crate alloc; - -use stylus_sdk::{ console, prelude::{entrypoint,public, sol_storage}}; -use pyth_stylus::pyth::pyth_contract::PythContract; - -sol_storage! { - #[entrypoint] - struct ProxyCallsExample { - bytes32 price_id; - address pyth_address; - - #[borrow] - PythContract _pyth; - } -} - - -#[public] -#[inherit(PythContract)] -impl ProxyCallsExample { - pub fn check_price(&mut self) { - console!("test") - // let _price = self._pyth.get_price_no_older_than(self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; - // Ok(()) - } -} \ No newline at end of file From 28d93161b34dc10906acabb404879b50457861d8 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 30 Oct 2024 12:28:08 +0000 Subject: [PATCH 055/160] changes --- target_chains/ethereum/sdk/stylus/Cargo.lock | 141 ++++++++++++++---- target_chains/ethereum/sdk/stylus/Cargo.toml | 4 +- .../ethereum/sdk/stylus/contracts/Cargo.toml | 8 +- .../function-calls/tests/function-calls.rs | 20 +++ 4 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 463436953e..ea57cf7c90 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -238,16 +238,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" dependencies = [ "alloy-rlp", + "arbitrary", "bytes", "cfg-if 1.0.0", "const-hex", + "derive_arbitrary", "derive_more", + "ethereum_ssz", "getrandom", "hex-literal", "itoa", "k256", "keccak-asm", "proptest", + "proptest-derive", "rand", "ruint", "serde", @@ -554,6 +558,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "ark-ff" version = "0.3.0" @@ -773,6 +783,7 @@ version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", + "dotenv", "e2e", "eyre", "futures", @@ -1291,6 +1302,17 @@ dependencies = [ "syn 1.0.109", ] +[[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.68", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -1325,6 +1347,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dunce" version = "1.0.4" @@ -1472,6 +1500,44 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "ethereum_ssz" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" +dependencies = [ + "ethereum-types", + "itertools 0.10.5", + "smallvec", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1521,6 +1587,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ + "arbitrary", "byteorder", "rand", "rustc-hex", @@ -1558,8 +1625,8 @@ dependencies = [ ] [[package]] -name = "function-calls" -version = "0.0.0" +name = "function-calls-example" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -1912,6 +1979,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -2206,25 +2291,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "motsu" -version = "0.1.0-rc" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cff6390ad19dea5f6bf7011af9560c3dc5f9162fa4b1b33c0d24df18a2c7fcb" -dependencies = [ - "const-hex", - "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell", - "stylus-sdk", - "tiny-keccak", -] - [[package]] name = "motsu" version = "0.1.0" dependencies = [ "const-hex", - "motsu-proc 0.1.0", + "motsu-proc", "once_cell", "stylus-sdk", "tiny-keccak", @@ -2236,24 +2308,13 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "motsu 0.1.0", + "motsu", "proc-macro2", "quote", "stylus-sdk", "syn 2.0.68", ] -[[package]] -name = "motsu-proc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.68", -] - [[package]] name = "native-tls" version = "0.2.12" @@ -2548,6 +2609,8 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", + "impl-rlp", + "impl-serde", "uint", ] @@ -2614,8 +2677,19 @@ dependencies = [ ] [[package]] -name = "proxy-call-contracts" -version = "0.0.0" +name = "proptest-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "proxy-calls-example" +version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", @@ -2652,9 +2726,12 @@ name = "pyth-stylus" version = "0.1.0" dependencies = [ "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", "alloy-sol-types", "mini-alloc", - "motsu 0.1.0-rc", + "motsu", "stylus-sdk", ] @@ -2905,6 +2982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", + "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", @@ -3666,6 +3744,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ + "arbitrary", "byteorder", "crunchy", "hex", diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index cdfff227e5..6a78af10ef 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -7,7 +7,7 @@ members = [ "lib/e2e", "lib/e2e-proc", "examples/function-calls", - "examples/proxy-call-contracts", + "examples/proxy-calls", "benches" ] default-members = [ @@ -17,7 +17,7 @@ default-members = [ "lib/motsu-proc", "lib/e2e-proc", "examples/function-calls", - "examples/proxy-call-contracts", + "examples/proxy-calls", ] # Explicitly set the resolver to version 2, which is the default for packages diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index da886c7740..dcd4d272ef 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -9,9 +9,15 @@ edition.workspace = true [dependencies] alloy-primitives.workspace = true alloy-sol-types.workspace = true +alloy-sol-macro.workspace = true +alloy-sol-macro-expander.workspace = true +alloy-sol-macro-input.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true -motsu = "=0.1.0-rc" + +[dev-dependencies] +motsu.workspace = true +alloy-primitives = { workspace = true, features = ["arbitrary"] } [features] # Enables using the standard library. This is not included in the default diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs new file mode 100644 index 0000000000..acad6a37ad --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -0,0 +1,20 @@ +#![cfg(feature = "e2e")] + +use alloy::hex; +use e2e::{receipt,ReceiptExt,Account}; +use eyre::Result; + +mod abi; + + +// // ============================================================================ +// // Integration Tests: Function Calls +// // ============================================================================ + + #[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + let contract_addr = alice.as_deployer().deploy().await?.address()?; +// // assert_eq!(true, true); + + Ok(()) + } From 74a4dd402c029c27720c01b11b544c3a53ad172e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 31 Oct 2024 07:54:28 +0000 Subject: [PATCH 056/160] chore:added constructor file and worked on bench --- .../sdk/stylus/benches/src/function_calls.rs | 35 ++++++++----------- .../sdk/stylus/benches/src/proxy_calls.rs | 8 ++--- .../function-calls/src/constructor.sol | 6 ++++ .../stylus/examples/function-calls/src/lib.rs | 20 ++++++++--- .../function-calls/tests/function_test.rs | 19 ---------- .../examples/proxy-calls/src/constructor.sol | 21 +---------- .../stylus/examples/proxy-calls/src/lib.rs | 22 +++++++----- 7 files changed, 54 insertions(+), 77 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 117bcc3c2d..a194ef65c1 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -5,20 +5,21 @@ use alloy::{ primitives::{Address, FixedBytes as TypeFixedBytes, fixed_bytes}, providers::ProviderBuilder, sol, - sol_types::{sol_data::FixedBytes, SolCall, SolConstructor}, + sol_types::{SolCall, SolConstructor}, }; use e2e::{receipt, Account}; -use dotenv; use crate::{ - report::{ContractReport, FunctionReport}, - CacheOpt, + env, report::{ContractReport, FunctionReport}, CacheOpt }; sol!( #[sol(rpc)] contract FunctionCall{ - function getPriceNoOlderThan() external view; + function getPriceUnsafe() external ; + function getEmaPriceUnsafe() external ; + function getPriceNoOlderThan() external ; + function getEmaPriceNoOlderThan() external; } ); @@ -60,26 +61,20 @@ pub async fn run_with( // let token_2 = uint!(2_U256); // let token_3 = uint!(3_U256); // let token_4 = uint!(4_U256); - - let _ = receipt!(contract.getPriceNoOlderThan())?; - // let _ = receipt!(contract.mint(alice_addr, token_3))?; + + println!("contract: {contract:?}"); + let _ = receipt!(contract.getPriceUnsafe())?; + let _ = receipt!(contract.getEmaPriceUnsafe())?; // let _ = receipt!(contract.mint(alice_addr, token_4))?; // IMPORTANT: Order matters! use FunctionCall::*; //#[rustfmt::skip] let receipts = vec![ + (getPriceUnsafeCall::SIGNATURE, receipt!(contract.getPriceUnsafe())?), + (getEmaPriceUnsafeCall::SIGNATURE, receipt!(contract.getEmaPriceUnsafe())?), (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan())?), - // (approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_2))?), - // (getApprovedCall::SIGNATURE, receipt!(contract.getApproved(token_2))?), - // (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), - // (ownerOfCall::SIGNATURE, receipt!(contract.ownerOf(token_2))?), - // (safeTransferFromCall::SIGNATURE, receipt!(contract.safeTransferFrom(alice_addr, bob_addr, token_3))?), - // (setApprovalForAllCall::SIGNATURE, receipt!(contract.setApprovalForAll(bob_addr, true))?), - // (totalSupplyCall::SIGNATURE, receipt!(contract.totalSupply())?), - // (transferFromCall::SIGNATURE, receipt!(contract.transferFrom(alice_addr, bob_addr, token_4))?), - // (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_1))?), - // (burnCall::SIGNATURE, receipt!(contract.burn(token_1))?), + (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan())?), ]; receipts @@ -92,7 +87,7 @@ async fn deploy( account: &Account, cache_opt: CacheOpt, ) -> eyre::Result
{ - let pyth_addr = dotenv::var("MOCK_PYTH_ADDRESS")?; + let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; let id= "ETH"; let mut bytes = [0u8; 32]; @@ -101,5 +96,5 @@ async fn deploy( println!("pyth address: {address:?} price_id: {price_id:?}"); let args = FunctionCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "function_calls", Some(args), cache_opt).await + crate::deploy(account, "function-calls", Some(args), cache_opt).await } diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 283da86d37..338bd1c8fc 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -8,11 +8,9 @@ use alloy::{ sol_types::{sol_data::FixedBytes, SolCall, SolConstructor}, }; use e2e::{receipt, Account}; -use dotenv; use crate::{ - report::{ContractReport, FunctionReport}, - CacheOpt, + env, report::{ContractReport, FunctionReport}, CacheOpt }; sol!( @@ -92,7 +90,7 @@ async fn deploy( account: &Account, cache_opt: CacheOpt, ) -> eyre::Result
{ - let pyth_addr = dotenv::var("MOCK_PYTH_ADDRESS")?; + let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; let id= "ETH"; let mut bytes = [0u8; 32]; @@ -101,5 +99,5 @@ async fn deploy( println!("pyth address: {address:?} price_id: {price_id:?}"); let args = ProxyCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "proxy_calls", Some(args), cache_opt).await + crate::deploy(account, "proxy-calls", Some(args), cache_opt).await } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol index 732f68e492..e8fcc679f2 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/constructor.sol @@ -21,9 +21,15 @@ contract FunctionCallsExample { // Storage variables for the Price and PriceFeed data Price private price; PriceFeed private priceFeed; + Price private ema_price; + PriceFeed private ema_priceFeed; constructor(address _pythAddress, bytes32 _priceId) { pythAddress = _pythAddress; priceId = _priceId; + price = Price(0, 0, 0, 0); + ema_price = Price(0, 0, 0, 0); + priceFeed = PriceFeed(_priceId, price, ema_price); + ema_priceFeed = PriceFeed(_priceId, ema_price, ema_price); } } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 0da855235e..f7caa494b8 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -2,8 +2,8 @@ extern crate alloc; use alloc::vec::Vec; -use alloy_primitives::Uint; -use stylus_sdk::prelude::{entrypoint,public, sol_storage}; +use alloy_primitives::U256; +use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage}}; use pyth_stylus::pyth::{functions::{ get_price_no_older_than, get_ema_price_no_older_than, @@ -34,9 +34,21 @@ sol_storage! { #[public] impl FunctionCallsExample { + pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { + let _ = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get()); + Ok(()) + } + + pub fn get_ema_price_unsafe(&mut self) -> Result<(), Vec> { + let _ = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get()); + Ok(()) + } pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { - let price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; - self.price.set(price); + let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000)); + Ok(()) + } + pub fn get_ema_price_no_older_than(&mut self) -> Result<(), Vec> { + let _ = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000)); Ok(()) } } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs deleted file mode 100644 index f693b3df89..0000000000 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function_test.rs +++ /dev/null @@ -1,19 +0,0 @@ -// #![cfg(feature = "e2e")] - -// // use alloy::hex; -// use e2e::{Account}; -// use eyre::Result; - -// // mod abi; - - -// // // ============================================================================ -// // // Integration Tests: Function Calls -// // // ============================================================================ - -// #[e2e::test] -// async fn constructs(alice: Account) -> Result<()> { -// assert_eq!(true, true); - -// Ok(()) -// } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol index 61a26c4d19..b1d1132989 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/constructor.sol @@ -3,27 +3,8 @@ pragma solidity ^0.8.0; contract ProxyCallsExample { address private pythAddress; - bytes32 private priceId; - struct Price { - int64 price; - uint64 conf; - int32 expo; - uint256 publishTime; // Using uint256 as Solidity does not have a native uint type - } - - struct PriceFeed { - bytes32 id; - Price price; - Price emaPrice; - } - - // Storage variables for the Price and PriceFeed data - Price private price; - PriceFeed private priceFeed; - - constructor(address _pythAddress, bytes32 _priceId) { + constructor(address _pythAddress) { pythAddress = _pythAddress; - priceId = _priceId; } } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs index ac2224dc0c..09876ff5fb 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs @@ -1,26 +1,30 @@ #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; + use alloc::vec::Vec; -use alloy_primitives::Uint; -use stylus_sdk::prelude::{entrypoint,public, sol_storage}; -use pyth_stylus::pyth::pyth_contract::PythContract; +use alloy_primitives::{ FixedBytes, U256}; +use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; +use pyth_stylus::{pyth::{ + pyth_contract::{IPyth, PythContract}, + types::Price +}, utils::helpers::decode_helper}; sol_storage! { #[entrypoint] struct ProxyCallsExample { - address pyth_address; - bytes32 price_id; + #[borrow] + PythContract pyth } } - #[public] +#[inherit(PythContract)] impl ProxyCallsExample { - pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { - // let price = get_price_no_older_than(self, self.pyth_address.get(),self.price_id.get() ,Uint::from(1047483647))?; - // self.price.set(price); + pub fn get_price_no_older_than(&mut self,id : FixedBytes<32>, age:U256) -> Result<(), Vec> { + let price = self.pyth.get_price_no_older_than(id, age)?; + let _ = decode_helper::(&price)?; Ok(()) } } \ No newline at end of file From b1dc6552478ff474890fab8a0fc6d58de7eae4d1 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 31 Oct 2024 07:56:13 +0000 Subject: [PATCH 057/160] chore: moved Ipyth to pyth-contract file --- .../ethereum/sdk/stylus/benches/Cargo.toml | 3 +- .../sdk/stylus/contracts/src/pyth/ipyth.rs | 45 ----------------- .../sdk/stylus/contracts/src/pyth/mod.rs | 1 - .../contracts/src/pyth/pyth_contract.rs | 48 +++++++++++++++++-- 4 files changed, 46 insertions(+), 51 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml index 67c1180c1f..11a2203333 100644 --- a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -16,5 +16,4 @@ eyre.workspace = true koba.workspace = true e2e.workspace = true serde = "1.0.203" -keccak-const = "0.2.0" -dotenv.workspace = true \ No newline at end of file +keccak-const = "0.2.0" \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs deleted file mode 100644 index 973e932ce5..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/ipyth.rs +++ /dev/null @@ -1,45 +0,0 @@ -use alloc::vec::Vec; -use alloy_primitives::U256; -use stylus_sdk::{abi::Bytes, alloy_primitives::FixedBytes}; - -pub trait IPyth { - type Error: Into>; - - fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; - - fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error>; - - fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; - - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error>; - - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; - - fn update_price_feeds_if_necessary( - &mut self, - update_data: Vec, - price_ids: Vec>, - publish_times: Vec, - ) -> Result<(), Self::Error>; - - fn get_update_fee(&mut self, update_data: Vec) -> Result; - - fn parse_price_feed_updates( - &mut self, - update_data: Vec, - price_ids: Vec>, - min_publish_time: u64, - max_publish_time: u64, - ) -> Result, Self::Error>; - - - fn parse_price_feed_updates_unique( - &mut self, - update_data: Vec, - price_ids: Vec>, - min_publish_time: u64, - max_publish_time: u64, - ) -> Result, Self::Error>; - - -} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 8e47444d53..8089bc058e 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,6 +1,5 @@ mod errors; mod events; -mod ipyth; pub mod types; mod mock; pub mod functions; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index 3ddbcfd79c..56ecb04264 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -1,5 +1,4 @@ use alloy_sol_types::SolValue; -use crate::pyth::ipyth::IPyth; use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; use alloc::vec::Vec; use alloy_primitives::{ FixedBytes, U256 , Bytes }; @@ -15,6 +14,49 @@ use crate::pyth::functions::{ parse_price_feed_updates_unique }; +pub trait IPyth { + type Error: Into>; + + fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + + fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: U256) -> Result, Self::Error>; + + fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error>; + + fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + + fn update_price_feeds_if_necessary( + &mut self, + update_data: Vec, + price_ids: Vec>, + publish_times: Vec, + ) -> Result<(), Self::Error>; + + fn get_update_fee(&mut self, update_data: Vec) -> Result; + + fn parse_price_feed_updates( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error>; + + + fn parse_price_feed_updates_unique( + &mut self, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Self::Error>; + + +} + + sol_storage! { pub struct PythContract { address _ipyth; @@ -34,8 +76,8 @@ impl IPyth for PythContract { Ok(data) } - fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: u8) -> Result, Self::Error> { - let price = get_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: U256) -> Result, Self::Error> { + let price = get_price_no_older_than(self, self._ipyth.get(), id,age)?; let data = price.abi_encode(); Ok(data) } From 5fafd3036f4a48b3d529ddb77e02c19fd02943fd Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 31 Oct 2024 07:57:10 +0000 Subject: [PATCH 058/160] chore: added variable to bash scripts --- target_chains/ethereum/sdk/stylus/scripts/bench.sh | 7 ++----- target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh | 2 +- target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh | 5 +---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 7e193cac7f..4565b3292c 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -19,17 +19,14 @@ deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ --private-key $PRIVATE_KEY \ --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') + +export MOCK_PYTH_ADDRESS=$deployed_to cd .. # Output the captured address -echo "Mock Pyth Deployed to address: $deployed_to" -echo "MOCK_PYTH_ADDRESS=$deployed_to" > .env -echo "Deployed address saved to .env file." NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort -export RPC_URL=http://localhost:8547 - # No need to compile benchmarks with `--release` # since this only runs the benchmarking code and the contracts have already been compiled with `--release` cargo run -p benches diff --git a/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh index 9ec839dd00..f1ae62db2d 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh @@ -22,7 +22,7 @@ get_example_crate_names () { find ./examples -maxdepth 2 -type f -name "Cargo.toml" | xargs grep 'name = ' | grep -oE '".*"' | tr -d "'\"" } -NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly} +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index fefc0b5a6a..f6448f1024 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -16,11 +16,8 @@ deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ --private-key $PRIVATE_KEY \ --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') +export MOCK_PYTH_ADDRESS=$deployed_to cd .. -# Output the captured address -echo "Mock Pyth Deployed to address: $deployed_to" -echo "MOCK_PYTH_ADDRESS=$deployed_to" > .env -echo "Deployed address saved to .env file." # Run e2e tests NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} From 5171513da216d7c058fd86d055179ffbf065ad01 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 31 Oct 2024 07:57:37 +0000 Subject: [PATCH 059/160] chore:decode and encode helper --- .../sdk/stylus/contracts/src/utils/helpers.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs index 41a28ae648..cce537cbce 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs @@ -72,3 +72,14 @@ pub fn call_helper( C::abi_decode_returns(&res, false /* validate */) .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } + +/// Decodes the data returned by an external contract call +pub fn decode_helper(data: &[u8]) -> Result> { + T::abi_decode(data, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} + +/// Encodes the data to be returned by an external contract call +pub fn encode_helper(data: T::RustType) -> Vec { + T::abi_encode(&data) +} \ No newline at end of file From 08fd4cb3e7c61e593b54afd7089d9caf9d15b122 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 1 Nov 2024 05:06:31 +0000 Subject: [PATCH 060/160] chore: fixed mock pyth deploy script --- .../pyth-solidity/script/MockPyth.s.sol | 40 +++++++++++++++++-- .../sdk/stylus/pyth-solidity/src/MockPyth.sol | 35 +--------------- .../stylus/pyth-solidity/test/MockPyth.t.sol | 1 - .../ethereum/sdk/stylus/scripts/bench.sh | 12 +++--- 4 files changed, 46 insertions(+), 42 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index c3ff5f40c8..5548343f6c 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -1,15 +1,49 @@ -// SPDX-License-Identifier: UNLICENSED +/// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; +import {MockPythSample} from "../src/MockPyth.sol"; contract MockPythScript is Script { + uint randNonce = 0; + string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; + bytes[] priceData = new bytes[](tickers.length); + bytes32[] priceIds = new bytes32[](tickers.length); + MockPythSample pyth_contract; - function setUp() public {} + function setUp() public { + + } function run() public { vm.startBroadcast(); - + deploy(100000, 100); + _generateInitialData(); + console.log("Pyth contract address:", address(pyth_contract)); + uint update_cost = pyth_contract.getUpdateFee(priceData); + pyth_contract.updatePriceFeeds{value: update_cost}(priceData); vm.stopBroadcast(); } + + function deploy(uint validTimePeriod, uint singleUpdateFeeInWei) internal { + pyth_contract = new MockPythSample(validTimePeriod, singleUpdateFeeInWei); + } + + function _generateInitialData() internal { + for (uint i = 0; i < tickers.length; i++) { + priceIds[i] = keccak256(abi.encodePacked(tickers[i])); + uint randomNumber = randNumber(priceIds[i], 10); + int64 price = int64(uint64(randomNumber + randNumber(priceIds[i], 2)) + 1); + uint64 conf = uint64(randomNumber + randNumber(priceIds[i], 3) + 1); + int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40)) + 1); + int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5)) + 1); + uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6) +1); + priceData[i] = pyth_contract.createPriceFeedUpdateData(priceIds[i], price, conf, expo, emaPrice, emaConf, uint64(block.timestamp), 0); + } + } + + function randNumber(bytes32 ticker, uint salt) internal returns (uint) { + randNonce++; + return uint(keccak256(abi.encodePacked(block.timestamp, ticker, msg.sender, randNonce, salt))) % randNonce; + } } diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol index a09825d7de..e6511b5348 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol @@ -2,43 +2,12 @@ pragma solidity ^0.8.13; import "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; +import {console} from "forge-std/console.sol"; contract MockPythSample is MockPyth { - uint randNonce = 0; - string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; - bytes32[] priceIds = new bytes32[](tickers.length); - constructor( uint validTimePeriod, uint singleUpdateFeeInWei ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { - getPriceIds(); - for (uint i = 0; i< tickers.length; i++) { - uint randomNumber = randNumber(priceIds[i],10); - int64 price = int64(uint64(randomNumber + randNumber(priceIds[i],2))); - uint64 conf = uint64(randomNumber+ randNumber(priceIds[i],3)); - int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40))); - int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5))); - uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6)); - createPriceFeedUpdateData(priceIds[i],price, conf, expo,emaPrice, emaConf, uint64(block.timestamp),0); - } - } - - function getPriceIds() internal { - for (uint i = 0; i < tickers.length; i++) { - bytes memory tempEmptyStringTest = bytes(tickers[i]); - if (tempEmptyStringTest.length == 0) { - priceIds[i] = bytes32(0); - } - else { - priceIds[i] = keccak256(abi.encodePacked(tickers[i])); - } - } + } - - function randNumber(bytes32 ticker, uint salt) internal returns(uint) - { - // increase nonce - randNonce++; - return uint(keccak256(abi.encodePacked(block.timestamp, ticker,msg.sender,randNonce, salt))) % randNonce; - } } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol index 1ce20d6d2c..f76d38b6a3 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; contract MockPythTest is Test { - function setUp() public { } } diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 4565b3292c..4472394862 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -14,11 +14,13 @@ cd "nitro-testnode" cd .. cd "pyth-solidity" -deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ - --rpc-url $RPC_URL \ - --private-key $PRIVATE_KEY \ - --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') - +deployed_to=$( + forge script ./script/MockPyth.s.sol:MockPythScript \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --broadcast \ + | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 +) export MOCK_PYTH_ADDRESS=$deployed_to cd .. From e5ba09d64f56ec4b09c4bc4c053801ec2c57069e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 1 Nov 2024 05:07:39 +0000 Subject: [PATCH 061/160] chore: removed dotenv crates --- target_chains/ethereum/sdk/stylus/Cargo.lock | 10 +++------- target_chains/ethereum/sdk/stylus/Cargo.toml | 2 +- target_chains/ethereum/sdk/stylus/rust-toolchain.toml | 2 ++ 3 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/rust-toolchain.toml diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index ea57cf7c90..2568b0cfd3 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -783,7 +783,7 @@ version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", - "dotenv", + "alloy-sol-types", "e2e", "eyre", "futures", @@ -1347,12 +1347,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dunce" version = "1.0.4" @@ -1630,6 +1624,7 @@ version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", + "alloy-sol-types", "e2e", "eyre", "mini-alloc", @@ -2693,6 +2688,7 @@ version = "0.1.0" dependencies = [ "alloy", "alloy-primitives", + "alloy-sol-types", "e2e", "eyre", "mini-alloc", diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 6a78af10ef..77876b2afe 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -29,7 +29,7 @@ resolver = "2" authors = ["Ifechukwu Daniel"] edition = "2021" license = "MIT" -repository = "https://github.com/OpenZeppelin/rust-contracts-stylus" +repository = "https://github.com/pyth-network/" version = "0.1.0" [workspace.lints.rust] diff --git a/target_chains/ethereum/sdk/stylus/rust-toolchain.toml b/target_chains/ethereum/sdk/stylus/rust-toolchain.toml new file mode 100644 index 0000000000..628740b12f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.79.0" From d1d5cecf48754cc854a3931723204b92cca32486 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 1 Nov 2024 05:08:17 +0000 Subject: [PATCH 062/160] chore: completed benches functions --- .../ethereum/sdk/stylus/benches/Cargo.toml | 1 + .../sdk/stylus/benches/src/function_calls.rs | 28 ++++++++++-------- .../ethereum/sdk/stylus/benches/src/main.rs | 4 +-- .../sdk/stylus/benches/src/proxy_calls.rs | 29 +++++++++++-------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml index 11a2203333..f5ed28e161 100644 --- a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] pyth-stylus.workspace = true +alloy-sol-types.workspace = true alloy-primitives = { workspace = true, features = ["tiny-keccak"] } alloy.workspace = true tokio.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index a194ef65c1..c0815d22ed 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -2,11 +2,12 @@ use std::str::FromStr; use alloy::{ network::{AnyNetwork, EthereumWallet}, - primitives::{Address, FixedBytes as TypeFixedBytes, fixed_bytes}, + primitives::{Address, FixedBytes as TypeFixedBytes}, providers::ProviderBuilder, sol, sol_types::{SolCall, SolConstructor}, }; +use alloy_sol_types::sol_data::String; use e2e::{receipt, Account}; use crate::{ @@ -20,6 +21,9 @@ sol!( function getEmaPriceUnsafe() external ; function getPriceNoOlderThan() external ; function getEmaPriceNoOlderThan() external; + function getUpdateFee() external; + function getValidTimePeriod() external; + function updatePriceFeeds() external payable; } ); @@ -57,15 +61,14 @@ pub async fn run_with( let contract = FunctionCall::new(contract_addr, &alice_wallet); println!("contract address: {contract_addr:?}"); - // let token_1 = uint!(1_U256); - // let token_2 = uint!(2_U256); - // let token_3 = uint!(3_U256); - // let token_4 = uint!(4_U256); - println!("contract: {contract:?}"); let _ = receipt!(contract.getPriceUnsafe())?; let _ = receipt!(contract.getEmaPriceUnsafe())?; - // let _ = receipt!(contract.mint(alice_addr, token_4))?; + let _ = receipt!(contract.getPriceNoOlderThan())?; + let _ = receipt!(contract.getEmaPriceNoOlderThan())?; + let _ = receipt!(contract.getUpdateFee())?; + let _ = receipt!(contract.getValidTimePeriod())?; + let _ = receipt!(contract.updatePriceFeeds())?; // IMPORTANT: Order matters! use FunctionCall::*; @@ -75,6 +78,9 @@ pub async fn run_with( (getEmaPriceUnsafeCall::SIGNATURE, receipt!(contract.getEmaPriceUnsafe())?), (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan())?), (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan())?), + (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee())?), + (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), + (updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds())?), ]; receipts @@ -89,11 +95,9 @@ async fn deploy( ) -> eyre::Result
{ let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; - let id= "ETH"; - let mut bytes = [0u8; 32]; - bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); - let price_id :TypeFixedBytes<32> = TypeFixedBytes::from(bytes); - println!("pyth address: {address:?} price_id: {price_id:?}"); + let id= keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); + let price_id = TypeFixedBytes::<32>::from_slice(&id); + println!(" addrss {address:?} price_id: {price_id:?}"); let args = FunctionCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "function-calls", Some(args), cache_opt).await diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 698d8d6913..0a3955076f 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -6,8 +6,8 @@ use futures::FutureExt; #[tokio::main] async fn main() -> eyre::Result<()> { let report = futures::future::try_join_all([ - function_calls::bench().boxed(), - proxy_calls::bench().boxed(), + function_calls::bench().boxed(), + //proxy_calls::bench().boxed(), ]) .await? .into_iter() diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 338bd1c8fc..7080c150f9 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -2,10 +2,10 @@ use std::str::FromStr; use alloy::{ network::{AnyNetwork, EthereumWallet}, - primitives::{Address, FixedBytes as TypeFixedBytes, fixed_bytes}, + primitives::{Address, FixedBytes as TypeFixedBytes, uint}, providers::ProviderBuilder, sol, - sol_types::{sol_data::FixedBytes, SolCall, SolConstructor}, + sol_types::{ SolCall, SolConstructor}, }; use e2e::{receipt, Account}; @@ -16,7 +16,7 @@ use crate::{ sol!( #[sol(rpc)] contract ProxyCall{ - function getPriceNoOlderThan() external view; + function getPriceNoOlderThan(bytes32 id, uint256 age) external; } ); @@ -54,12 +54,14 @@ pub async fn run_with( let contract = ProxyCall::new(contract_addr, &alice_wallet); println!("contract address: {contract_addr:?}"); - // let token_1 = uint!(1_U256); + let eth_id = bytes_from_str("ETH"); + let sol_id = bytes_from_str("SOL"); + let time_frame = uint!(10000_U256); // let token_2 = uint!(2_U256); // let token_3 = uint!(3_U256); // let token_4 = uint!(4_U256); - let _ = receipt!(contract.getPriceNoOlderThan())?; + let _ = receipt!(contract.getPriceNoOlderThan(eth_id,time_frame))?; // let _ = receipt!(contract.mint(alice_addr, token_3))?; // let _ = receipt!(contract.mint(alice_addr, token_4))?; @@ -67,7 +69,7 @@ pub async fn run_with( use ProxyCall::*; //#[rustfmt::skip] let receipts = vec![ - (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan())?), + (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan(sol_id, time_frame))?), // (approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_2))?), // (getApprovedCall::SIGNATURE, receipt!(contract.getApproved(token_2))?), // (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), @@ -92,12 +94,15 @@ async fn deploy( ) -> eyre::Result
{ let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; - let id= "ETH"; - let mut bytes = [0u8; 32]; - bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); - let price_id :TypeFixedBytes<32> = TypeFixedBytes::from(bytes); - println!("pyth address: {address:?} price_id: {price_id:?}"); - let args = ProxyCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; + println!("pyth address: {address:?}"); + let args = ProxyCallsExample::constructorCall { _pythAddress: address }; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "proxy-calls", Some(args), cache_opt).await } + + +pub fn bytes_from_str(id: &str) -> TypeFixedBytes<32> { + let mut bytes = [0u8; 32]; + bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); + TypeFixedBytes::<32>::from(bytes) +} \ No newline at end of file From 64e3be5df1db4aa909262f7b14ef6d0365790469 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 1 Nov 2024 05:09:47 +0000 Subject: [PATCH 063/160] chore: examples and proxy call fixes --- .../stylus/examples/function-calls/Cargo.toml | 1 + .../stylus/examples/function-calls/src/lib.rs | 52 ++++++++++++++++--- .../stylus/examples/proxy-calls/Cargo.toml | 1 + .../examples/proxy-calls/tests/proxy-calls.rs | 2 +- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index ca4753fe29..ccae44a760 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -9,6 +9,7 @@ version.workspace = true [dependencies] pyth-stylus.workspace = true alloy-primitives.workspace = true +alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index f7caa494b8..2e2aa97613 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -3,7 +3,7 @@ extern crate alloc; use alloc::vec::Vec; use alloy_primitives::U256; -use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage}}; +use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage, SolidityError}}; use pyth_stylus::pyth::{functions::{ get_price_no_older_than, get_ema_price_no_older_than, @@ -17,6 +17,7 @@ use pyth_stylus::pyth::{functions::{ StoragePrice, StoragePriceFeed }}; +use alloy_sol_types::sol; sol_storage! { @@ -32,23 +33,62 @@ sol_storage! { } +sol! { + + error ArraySizeNotMatch(); + + error CallFailed(); + +} + +#[derive(SolidityError)] +pub enum MultiCallErrors { + + ArraySizeNotMatch(ArraySizeNotMatch), + + CallFailed(CallFailed), + +} + + #[public] impl FunctionCallsExample { pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { - let _ = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get()); - Ok(()) + let price = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; + if price.price > 0 { + return Ok(()); + } + Err(MultiCallErrors::CallFailed(CallFailed{}).into()) } pub fn get_ema_price_unsafe(&mut self) -> Result<(), Vec> { - let _ = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get()); + let _ = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; Ok(()) } pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { - let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000)); + let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; Ok(()) } + pub fn get_ema_price_no_older_than(&mut self) -> Result<(), Vec> { - let _ = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000)); + let _ = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; + Ok(()) + } + + pub fn get_update_fee(&mut self) -> Result<(), Vec> { + let _ = get_update_fee(self, self.pyth_address.get(), Vec::new())?; Ok(()) } + + pub fn get_valid_time_period(&mut self) -> Result<(), Vec> { + let _ = get_valid_time_period(self, self.pyth_address.get())?; + Ok(()) + } + + #[payable] + pub fn update_price_feeds(&mut self) -> Result<(), Vec> { + let _ = update_price_feeds(self, self.pyth_address.get(), Vec::new())?; + Ok(()) + } + } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml index 5785f25f60..0429f8a5e5 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml @@ -9,6 +9,7 @@ version.workspace = true [dependencies] pyth-stylus.workspace = true alloy-primitives.workspace = true +alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs index 6380303eb0..d56528ce41 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs @@ -13,7 +13,7 @@ mod abi; #[e2e::test] async fn constructs(alice: Account) -> Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; + // let contract_addr = alice.as_deployer().deploy().await?.address()?; // // assert_eq!(true, true); Ok(()) From 69b61df685f33873252f798d6073269b79b1d9a3 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 1 Nov 2024 05:10:05 +0000 Subject: [PATCH 064/160] changes --- .../stylus/contracts/src/pyth/functions.rs | 11 +- .../sdk/stylus/contracts/src/pyth/mock.rs | 6 +- .../sdk/stylus/contracts/src/pyth/types.rs | 172 +++++++++--------- 3 files changed, 94 insertions(+), 95 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index ff7e144343..fcf12ea6f4 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -12,14 +12,14 @@ use crate::pyth::types::{ Price, PriceFeed }; -use crate::utils::helpers::{call_helper, delegate_call_helper}; +use crate::utils::helpers::{call_helper, delegate_call_helper, static_call_helper}; use alloc::vec::Vec; -use stylus_sdk::storage::TopLevelStorage; +use stylus_sdk::{storage::TopLevelStorage, console}; use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; pub fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { - let price_call = call_helper::(storage, pyth_address, (id,age,))?; + let price_call = call_helper::(storage, pyth_address, (id,age,),)?; Ok(price_call.price) } @@ -39,9 +39,10 @@ pub fn get_ema_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_addre Ok(ema_price.price) } -pub fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { +pub fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { let price = call_helper::(storage, pyth_address, (id,))?; - Ok(price.price) + let price = Price{price: price._0, conf: price._1, expo: price._2, publish_time: price._3}; + Ok(price) } pub fn get_valid_time_period(storage: &mut impl TopLevelStorage,pyth_address: Address) -> Result> { diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 6d044fb462..36a42e31f7 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -292,8 +292,8 @@ mod tests { fn can_update_price_feeds(contract: MockPythContract) { let _ = contract.initialize(U256::from(1000), U256::from(1000)); let id = generate_bytes(); - let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, U256::from(1000), PREV_PUBLISH_TIME); - let mut update_data: Vec = vec![]; - update_data.push(Bytes::from(price_feed_created)); + //let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, U256::from(1000), PREV_PUBLISH_TIME); + //let mut update_data: Vec = vec![]; + //update_data.push(Bytes::from(price_feed_created)); } } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index dc31edfb5b..cad896107f 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -68,94 +68,92 @@ sol! { Price price; Price ema_price; } - - // Function call selector: Fetches the price associated with the given ID without validation. - // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. - function getPriceUnsafe(bytes32 id) external view returns (Price memory price); - - // Function call selector: Retrieves the price associated with the given ID - // ensuring that the price is not older than the specified age in seconds. - // Returns the `Price` data for the specified `bytes32` ID. - function getPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (Price memory price); - - // Function call selector: Fetches the Exponential Moving Average (EMA) price - // associated with the given ID without validation. - // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. - function getEmaPriceUnsafe( + // Function call selector: Fetches the price associated with the given ID without validation. + // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + function getPriceUnsafe(bytes32 id) external view returns (int64,uint64,int32,uint); + + // Function call selector: Retrieves the price associated with the given ID + // ensuring that the price is not older than the specified age in seconds. + // Returns the `Price` data for the specified `bytes32` ID. + function getPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (Price memory price); + + // Function call selector: Fetches the Exponential Moving Average (EMA) price + // associated with the given ID without validation. + // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + function getEmaPriceUnsafe( + bytes32 id + ) external view returns (Price memory price); + + // Function call selector: Retrieves the EMA price associated with the given ID + // ensuring that the price is not older than the specified age in seconds. + // Returns the `Price` data for the specified `bytes32` ID. + function getEmaPriceNoOlderThan( + bytes32 id, + uint age + ) external view returns (Price memory price); + + // Function call selector: Updates multiple price feeds using the provided update data. + // Accepts an array of `bytes` containing update data and processes the updates. + function updatePriceFeeds(bytes[] calldata updateData) external payable; + + // Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. + // Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds + // and processes the updates if conditions are met. + function updatePriceFeedsIfNecessary( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64[] calldata publishTimes + ) external payable; + + // Function call selector: Calculates the fee amount required to update price feeds + // based on the provided update data. + // Returns the fee amount as a `uint`. + function getUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + + // Function call selector: Parses updates from the provided data for multiple price feeds. + // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + // and returns an array of `PriceFeed` structs containing the parsed updates. + function parsePriceFeedUpdates( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Parses updates from the provided data for unique price feeds. + // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + // and returns an array of unique `PriceFeed` structs containing the parsed updates. + function parsePriceFeedUpdatesUnique( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minPublishTime, + uint64 maxPublishTime + ) external payable returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Queries the price feed for a given ID. + // Returns an array of `PriceFeed` structs associated with the specified `bytes32` ID. + function queryPriceFeed( + bytes32 id + ) public view virtual returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Checks if a price feed exists for the given ID. + // Returns an array of `PriceFeed` structs if it exists for the specified `bytes32` ID. + function priceFeedExists( bytes32 id - ) external view returns (Price memory price); - - // Function call selector: Retrieves the EMA price associated with the given ID - // ensuring that the price is not older than the specified age in seconds. - // Returns the `Price` data for the specified `bytes32` ID. - function getEmaPriceNoOlderThan( - bytes32 id, - uint age - ) external view returns (Price memory price); - - // Function call selector: Updates multiple price feeds using the provided update data. - // Accepts an array of `bytes` containing update data and processes the updates. - function updatePriceFeeds(bytes[] calldata updateData) external payable; - - // Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. - // Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds - // and processes the updates if conditions are met. - function updatePriceFeedsIfNecessary( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64[] calldata publishTimes - ) external payable; - - // Function call selector: Calculates the fee amount required to update price feeds - // based on the provided update data. - // Returns the fee amount as a `uint`. - function getUpdateFee( - bytes[] calldata updateData - ) external view returns (uint feeAmount); - - // Function call selector: Parses updates from the provided data for multiple price feeds. - // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, - // and returns an array of `PriceFeed` structs containing the parsed updates. - function parsePriceFeedUpdates( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds); - - // Function call selector: Parses updates from the provided data for unique price feeds. - // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, - // and returns an array of unique `PriceFeed` structs containing the parsed updates. - function parsePriceFeedUpdatesUnique( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) external payable returns (PriceFeed[] memory priceFeeds); - - // Function call selector: Queries the price feed for a given ID. - // Returns an array of `PriceFeed` structs associated with the specified `bytes32` ID. - function queryPriceFeed( - bytes32 id - ) public view virtual returns (PriceFeed[] memory priceFeeds); - - // Function call selector: Checks if a price feed exists for the given ID. - // Returns an array of `PriceFeed` structs if it exists for the specified `bytes32` ID. - function priceFeedExists( - bytes32 id - ) public view virtual returns (PriceFeed[] memory priceFeeds); - - // Function call selector: Retrieves the valid time period for price feeds. - // Returns the valid time period as a `uint`. - function getValidTimePeriod() - public - view - virtual - returns (uint validTimePeriod); - + ) public view virtual returns (PriceFeed[] memory priceFeeds); + + // Function call selector: Retrieves the valid time period for price feeds. + // Returns the valid time period as a `uint`. + function getValidTimePeriod() + public + view + virtual + returns (uint validTimePeriod); } impl StoragePrice { From a21b5fa18d35062db0de42491773f85ded3e1a6f Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 2 Nov 2024 01:36:12 +0000 Subject: [PATCH 065/160] chore: added create functions --- .../stylus/contracts/src/pyth/functions.rs | 28 +++++++++++++++++-- .../sdk/stylus/contracts/src/pyth/mock.rs | 4 +-- .../sdk/stylus/contracts/src/pyth/mod.rs | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index fcf12ea6f4..c23d84c726 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -14,9 +14,10 @@ use crate::pyth::types::{ }; use crate::utils::helpers::{call_helper, delegate_call_helper, static_call_helper}; use alloc::vec::Vec; -use stylus_sdk::{storage::TopLevelStorage, console}; +use stylus_sdk::storage::TopLevelStorage; use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; - +use crate::pyth::mock::DecodeDataType; +use alloy_sol_types::SolType; pub fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { let price_call = call_helper::(storage, pyth_address, (id,age,),)?; @@ -68,4 +69,25 @@ pub fn parse_price_feed_updates(storage: &mut impl TopLevelStorage,pyth_address: pub fn parse_price_feed_updates_unique(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; Ok(parse_price_feed_updates_call.priceFeeds) -} \ No newline at end of file +} +/// Create Price Feed +pub fn create_price_feed_update_data( + id:FixedBytes<32>, + price:i64, + conf:u64, + expo:i32, + ema_price:i64, + ema_conf:u64, + publish_time:U256, + prev_publish_time:u64 + ) -> Vec { + let price = Price { price: price, conf, expo, publish_time }; + let ema_price = Price { price:ema_price, conf:ema_conf, expo:expo,publish_time}; + + let price_feed_data = PriceFeed { + id,price,ema_price + }; + + let price_feed_data_encoding = (price_feed_data, prev_publish_time); + return DecodeDataType::abi_encode(&price_feed_data_encoding); + } \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 36a42e31f7..fee60885c9 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -7,8 +7,8 @@ use crate::pyth::errors::{Error, PriceFeedNotFound}; use crate::pyth::types::{ PriceFeed,Price, StoragePriceFeed}; use crate::pyth::events::PriceFeedUpdate; -//decode data type -pub(crate) type DecodeDataType = (PriceFeed, SolUInt<64>); +//decode data type PriceFeed and uint64 +pub type DecodeDataType = (PriceFeed, SolUInt<64>); sol_storage! { struct MockPythContract { diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 8089bc058e..bda67f3836 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,7 +1,7 @@ mod errors; mod events; pub mod types; -mod mock; +pub mod mock; pub mod functions; pub mod pyth_contract; From b3547470a0af7e265e1d22d067df6ce7c2f012cf Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 07:35:04 +0000 Subject: [PATCH 066/160] chore: completed get data benchmarks --- .../sdk/stylus/benches/src/function_calls.rs | 14 +--- .../ethereum/sdk/stylus/benches/src/lib.rs | 3 +- .../ethereum/sdk/stylus/benches/src/main.rs | 2 +- .../sdk/stylus/benches/src/proxy_calls.rs | 67 ++++++++----------- 4 files changed, 32 insertions(+), 54 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index c0815d22ed..7c31b67ee2 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -7,7 +7,6 @@ use alloy::{ sol, sol_types::{SolCall, SolConstructor}, }; -use alloy_sol_types::sol_data::String; use e2e::{receipt, Account}; use crate::{ @@ -24,6 +23,7 @@ sol!( function getUpdateFee() external; function getValidTimePeriod() external; function updatePriceFeeds() external payable; + function updatePriceFeedsIfNecessary() external payable; } ); @@ -47,32 +47,24 @@ pub async fn run_with( cache_opt: CacheOpt, ) -> eyre::Result> { let alice = Account::new().await?; - let alice_addr = alice.address(); let alice_wallet = ProviderBuilder::new() .network::() .with_recommended_fillers() .wallet(EthereumWallet::from(alice.signer.clone())) .on_http(alice.url().parse()?); - - let bob = Account::new().await?; - let bob_addr = bob.address(); - let contract_addr = deploy(&alice, cache_opt).await?; let contract = FunctionCall::new(contract_addr, &alice_wallet); - println!("contract address: {contract_addr:?}"); - println!("contract: {contract:?}"); let _ = receipt!(contract.getPriceUnsafe())?; let _ = receipt!(contract.getEmaPriceUnsafe())?; let _ = receipt!(contract.getPriceNoOlderThan())?; let _ = receipt!(contract.getEmaPriceNoOlderThan())?; let _ = receipt!(contract.getUpdateFee())?; let _ = receipt!(contract.getValidTimePeriod())?; - let _ = receipt!(contract.updatePriceFeeds())?; // IMPORTANT: Order matters! use FunctionCall::*; - //#[rustfmt::skip] + #[rustfmt::skip] let receipts = vec![ (getPriceUnsafeCall::SIGNATURE, receipt!(contract.getPriceUnsafe())?), (getEmaPriceUnsafeCall::SIGNATURE, receipt!(contract.getEmaPriceUnsafe())?), @@ -80,7 +72,6 @@ pub async fn run_with( (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan())?), (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee())?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), - (updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds())?), ]; receipts @@ -97,7 +88,6 @@ async fn deploy( let address = Address::from_str(&pyth_addr)?; let id= keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); let price_id = TypeFixedBytes::<32>::from_slice(&id); - println!(" addrss {address:?} price_id: {price_id:?}"); let args = FunctionCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "function-calls", Some(args), cache_opt).await diff --git a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs index 4628241fa2..6326844840 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs @@ -49,8 +49,7 @@ async fn deploy( .join("target") .join("wasm32-unknown-unknown") .join("release") - .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); - println!("wasm path: {wasm_path:?}"); + .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); let sol_path = args.as_ref().map(|_| { manifest_dir .join("examples") diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 0a3955076f..5996710809 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -7,7 +7,7 @@ use futures::FutureExt; async fn main() -> eyre::Result<()> { let report = futures::future::try_join_all([ function_calls::bench().boxed(), - //proxy_calls::bench().boxed(), + proxy_calls::bench().boxed(), ]) .await? .into_iter() diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 7080c150f9..9f7f0c1bfb 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -1,13 +1,12 @@ use std::str::FromStr; use alloy::{ - network::{AnyNetwork, EthereumWallet}, - primitives::{Address, FixedBytes as TypeFixedBytes, uint}, - providers::ProviderBuilder, - sol, - sol_types::{ SolCall, SolConstructor}, + network::{AnyNetwork, EthereumWallet}, primitives::{uint, Address, FixedBytes as TypeFixedBytes, Bytes, U256}, + providers::ProviderBuilder, + sol, sol_types::{ SolCall, SolConstructor} }; use e2e::{receipt, Account}; +use pyth_stylus::pyth::functions::create_price_feed_update_data; use crate::{ env, report::{ContractReport, FunctionReport}, CacheOpt @@ -16,7 +15,12 @@ use crate::{ sol!( #[sol(rpc)] contract ProxyCall{ - function getPriceNoOlderThan(bytes32 id, uint256 age) external; + function getPriceUnsafe(bytes32 id) external; + function getEmaPriceUnsafe(bytes32 id) external; + function getPriceNoOlderThan(bytes32 id, uint age) external; + function getEmaPriceNoOlderThan(bytes32 id, uint age) external; + function getUpdateFee(bytes[] calldata updateData) external returns (uint256); + function getValidTimePeriod() external; } ); @@ -40,46 +44,39 @@ pub async fn run_with( cache_opt: CacheOpt, ) -> eyre::Result> { let alice = Account::new().await?; - let alice_addr = alice.address(); let alice_wallet = ProviderBuilder::new() .network::() .with_recommended_fillers() .wallet(EthereumWallet::from(alice.signer.clone())) .on_http(alice.url().parse()?); - let bob = Account::new().await?; - let bob_addr = bob.address(); - let contract_addr = deploy(&alice, cache_opt).await?; let contract = ProxyCall::new(contract_addr, &alice_wallet); - println!("contract address: {contract_addr:?}"); - let eth_id = bytes_from_str("ETH"); - let sol_id = bytes_from_str("SOL"); + let id = keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); + let id = TypeFixedBytes::<32>::from_slice(&id); let time_frame = uint!(10000_U256); - // let token_2 = uint!(2_U256); - // let token_3 = uint!(3_U256); - // let token_4 = uint!(4_U256); + let age = uint!(10000_U256); + + let data = create_price_feed_update_data(id, 10, 100, 100, 100, 100, U256::from(100), 0); + let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); - let _ = receipt!(contract.getPriceNoOlderThan(eth_id,time_frame))?; - // let _ = receipt!(contract.mint(alice_addr, token_3))?; - // let _ = receipt!(contract.mint(alice_addr, token_4))?; + let _ = receipt!(contract.getPriceUnsafe(id))?; + let _ = receipt!(contract.getEmaPriceUnsafe(id))?; + let _ = receipt!(contract.getPriceNoOlderThan(id,age))?; + let _ = receipt!(contract.getEmaPriceNoOlderThan(id,age))?; + let _ = receipt!(contract.getValidTimePeriod())?; // IMPORTANT: Order matters! use ProxyCall::*; - //#[rustfmt::skip] + #[rustfmt::skip] let receipts = vec![ - (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan(sol_id, time_frame))?), - // (approveCall::SIGNATURE, receipt!(contract.approve(bob_addr, token_2))?), - // (getApprovedCall::SIGNATURE, receipt!(contract.getApproved(token_2))?), - // (isApprovedForAllCall::SIGNATURE, receipt!(contract.isApprovedForAll(alice_addr, bob_addr))?), - // (ownerOfCall::SIGNATURE, receipt!(contract.ownerOf(token_2))?), - // (safeTransferFromCall::SIGNATURE, receipt!(contract.safeTransferFrom(alice_addr, bob_addr, token_3))?), - // (setApprovalForAllCall::SIGNATURE, receipt!(contract.setApprovalForAll(bob_addr, true))?), - // (totalSupplyCall::SIGNATURE, receipt!(contract.totalSupply())?), - // (transferFromCall::SIGNATURE, receipt!(contract.transferFrom(alice_addr, bob_addr, token_4))?), - // (mintCall::SIGNATURE, receipt!(contract.mint(alice_addr, token_1))?), - // (burnCall::SIGNATURE, receipt!(contract.burn(token_1))?), + (getPriceUnsafeCall::SIGNATURE, receipt!(contract.getPriceUnsafe(id))?), + (getEmaPriceUnsafeCall::SIGNATURE, receipt!(contract.getEmaPriceUnsafe(id))?), + (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan(id, time_frame))?), + (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan(id, time_frame))?), + (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), + (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee(data_bytes.clone()))?), ]; receipts @@ -94,15 +91,7 @@ async fn deploy( ) -> eyre::Result
{ let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; - println!("pyth address: {address:?}"); let args = ProxyCallsExample::constructorCall { _pythAddress: address }; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "proxy-calls", Some(args), cache_opt).await } - - -pub fn bytes_from_str(id: &str) -> TypeFixedBytes<32> { - let mut bytes = [0u8; 32]; - bytes[..id.len().min(32)].copy_from_slice(&id.as_bytes()[..id.len().min(32)]); - TypeFixedBytes::<32>::from(bytes) -} \ No newline at end of file From d1a4eae0bc0834b09610affa2cb422eeb77c61fb Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 07:36:38 +0000 Subject: [PATCH 067/160] chore:contract changes --- .../ethereum/sdk/stylus/contracts/src/lib.rs | 6 + .../stylus/contracts/src/pyth/functions.rs | 281 ++++++++++++++---- .../sdk/stylus/contracts/src/pyth/mock.rs | 2 +- .../sdk/stylus/contracts/src/pyth/mod.rs | 7 + .../contracts/src/pyth/pyth_contract.rs | 113 ++++++- .../sdk/stylus/contracts/src/pyth/types.rs | 127 ++++---- .../sdk/stylus/contracts/src/utils/mod.rs | 1 + 7 files changed, 400 insertions(+), 137 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index c5bec36839..76aad27ec6 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] #![allow(clippy::pub_underscore_fields, clippy::module_name_repetitions)] #![cfg_attr(not(feature = "std"), no_std, no_main)] #![deny(rustdoc::broken_intra_doc_links)] @@ -7,7 +8,12 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +/// Utility functions for interacting with the Pyth oracle. +/// This module contains functions for interacting with the Pyth oracle. pub mod utils; + +/// Pyth contract for interacting with the Pyth oracle. +/// This module contains the types and functions for interacting with the Pyth oracle. pub mod pyth; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index c23d84c726..b17582d4ae 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -12,82 +12,255 @@ use crate::pyth::types::{ Price, PriceFeed }; -use crate::utils::helpers::{call_helper, delegate_call_helper, static_call_helper}; +use crate::utils::helpers::{call_helper, delegate_call_helper}; use alloc::vec::Vec; use stylus_sdk::storage::TopLevelStorage; use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; use crate::pyth::mock::DecodeDataType; use alloy_sol_types::SolType; -pub fn get_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { - let price_call = call_helper::(storage, pyth_address, (id,age,),)?; - Ok(price_call.price) +/// Retrieves the price for a given asset ID from the Pyth price feed, ensuring the price is not older than a specified age. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `id`: The fixed byte identifier for the asset whose price is being retrieved. +/// - `age`: The maximum allowed age of the price. +/// +/// # Returns +/// - `Result>`: A `Result` that contains the `Price` struct if successful, or an error message as a byte vector. +pub fn get_price_no_older_than( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + id: FixedBytes<32>, + age: U256, +) -> Result> { + let price_call = call_helper::(storage, pyth_address, (id, age,))?; + Ok(price_call.price) } -pub fn get_update_fee(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result> { - let update_fee_call = call_helper::(storage, pyth_address, (update_data,))?; - Ok(update_fee_call.feeAmount) +/// Queries the contract for the fee required to update price data, given the update data as input. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `update_data`: A vector of bytes containing the data required for the update. +/// +/// # Returns +/// - `Result>`: A `Result` containing the update fee as a `U256` if successful, or an error message as a byte vector. +pub fn get_update_fee( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + update_data: Vec, +) -> Result> { + let update_fee_call = call_helper::(storage, pyth_address, (update_data,))?; + Ok(update_fee_call.feeAmount) } -pub fn get_ema_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { - let ema_price = call_helper::(storage, pyth_address, (id,))?; - Ok(ema_price.price) +/// Retrieves the Exponential Moving Average (EMA) price for a specific asset ID without safety checks. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `id`: The fixed byte identifier for the asset whose EMA price is being retrieved. +/// +/// # Returns +/// - `Result>`: A `Result` containing the `Price` struct if successful, or an error message as a byte vector. +pub fn get_ema_price_unsafe( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + id: FixedBytes<32>, +) -> Result> { + let ema_price = call_helper::(storage, pyth_address, (id,))?; + Ok(ema_price.price) } +/// Similar to `get_ema_price_unsafe`, but ensures the EMA price is not older than a specified age. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `id`: The fixed byte identifier for the asset whose EMA price is being retrieved. +/// - `age`: The maximum allowed age of the EMA price. +/// +/// # Returns +/// - `Result>`: A `Result` containing the `Price` struct if successful, or an error message as a byte vector. +pub fn get_ema_price_no_older_than( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + id: FixedBytes<32>, + age: U256, +) -> Result> { + let ema_price = call_helper::(storage, pyth_address, (id, age,))?; + Ok(ema_price.price) +} -pub fn get_ema_price_no_older_than(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>, age:U256) -> Result> { - let ema_price = call_helper::(storage, pyth_address, (id,age,))?; - Ok(ema_price.price) +/// Retrieves the current price for a given asset ID without safety checks, constructing a `Price` struct from raw values. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `id`: The fixed byte identifier for the asset whose price is being retrieved. +/// +/// # Returns +/// - `Result>`: A `Result` containing the constructed `Price` struct if successful, or an error message as a byte vector. +pub fn get_price_unsafe( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + id: FixedBytes<32>, +) -> Result> { + let price = call_helper::(storage, pyth_address, (id,))?; + let price = Price { + price: price._0, + conf: price._1, + expo: price._2, + publish_time: price._3, + }; + Ok(price) } -pub fn get_price_unsafe(storage: &mut impl TopLevelStorage,pyth_address: Address,id: FixedBytes<32>) -> Result> { - let price = call_helper::(storage, pyth_address, (id,))?; - let price = Price{price: price._0, conf: price._1, expo: price._2, publish_time: price._3}; - Ok(price) +/// Queries the Pyth contract to get the valid time period for price feeds. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// +/// # Returns +/// - `Result>`: A `Result` containing the valid time period as a `U256` if successful, or an error message as a byte vector. +pub fn get_valid_time_period( + storage: &mut impl TopLevelStorage, + pyth_address: Address, +) -> Result> { + let valid_time_period = call_helper::(storage, pyth_address, ())?; + Ok(valid_time_period.validTimePeriod) } -pub fn get_valid_time_period(storage: &mut impl TopLevelStorage,pyth_address: Address) -> Result> { - let valid_time_period = call_helper::(storage, pyth_address, ())?; - Ok(valid_time_period.validTimePeriod) +/// Updates the price feeds in the Pyth contract using the provided update data. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `update_data`: A vector of bytes containing the data required for the update. +/// +/// # Returns +/// - `Result<(), Vec>`: A `Result` that indicates success or failure, returning an error message as a byte vector if unsuccessful. +pub fn update_price_feeds( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + update_data: Vec, +) -> Result<(), Vec> { + delegate_call_helper::(storage, pyth_address, (update_data,))?; + Ok(()) } -pub fn update_price_feeds(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec) -> Result<(), Vec> { - delegate_call_helper::(storage, pyth_address, (update_data,))?; - Ok(()) +/// Updates price feeds only if necessary, based on provided price IDs and publish times. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `update_data`: A vector of bytes containing the data required for the update. +/// - `price_ids`: A vector of fixed byte identifiers for the assets to update. +/// - `publish_times`: A vector of timestamps indicating when the prices were published. +/// +/// # Returns +/// - `Result<(), Vec>`: A `Result` that indicates success or failure, returning an error message as a byte vector if unsuccessful. +pub fn update_price_feeds_if_necessary( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + update_data: Vec, + price_ids: Vec>, + publish_times: Vec, +) -> Result<(), Vec> { + delegate_call_helper::( + storage, + pyth_address, + (update_data, price_ids, publish_times), + )?; + Ok(()) } -pub fn update_price_feeds_if_necessary(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, publish_times: Vec) -> Result<(), Vec> { - delegate_call_helper::(storage, pyth_address, (update_data,price_ids, publish_times))?; - Ok(()) +/// Parses the updates to price feeds, returning a vector of `PriceFeed` structs based on the provided time range. +/// +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `update_data`: A vector of bytes containing the data required for the updates. +/// - `price_ids`: A vector of fixed byte identifiers for the assets being updated. +/// - `min_publish_time`: The minimum publish time to consider for the updates. +/// - `max_publish_time`: The maximum publish time to consider for the updates. +/// +/// # Returns +/// - `Result, Vec>`: A `Result` containing a vector of `PriceFeed` structs if successful, or an error message as a byte vector. +pub fn parse_price_feed_updates( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64, +) -> Result, Vec> { + let parse_price_feed_updates_call = delegate_call_helper::( + storage, + pyth_address, + (update_data, price_ids, min_publish_time, max_publish_time), + )?; + Ok(parse_price_feed_updates_call.priceFeeds) } - -pub fn parse_price_feed_updates(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { - let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; - Ok(parse_price_feed_updates_call.priceFeeds) -} -pub fn parse_price_feed_updates_unique(storage: &mut impl TopLevelStorage,pyth_address: Address,update_data:Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64) -> Result, Vec> { - let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data,price_ids, min_publish_time, max_publish_time))?; - Ok(parse_price_feed_updates_call.priceFeeds) +/// # Parameters +/// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. +/// - `pyth_address`: The address of the Pyth price feed contract. +/// - `update_data`: A vector of bytes containing the data required for the updates. +/// - `price_ids`: A vector of fixed byte identifiers for the assets being updated. +/// - `min_publish_time`: The minimum publish time to consider for the updates +/// Parses the price feed updates uniquely and returns the corresponding price feeds. +pub fn parse_price_feed_updates_unique( + storage: &mut impl TopLevelStorage, + pyth_address: Address, + update_data: Vec, + price_ids: Vec>, + min_publish_time: u64, + max_publish_time: u64 +) -> Result, Vec> { + let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data, price_ids, min_publish_time, max_publish_time))?; + Ok(parse_price_feed_updates_call.priceFeeds) } -/// Create Price Feed + + +/// Creates the update data required for a price feed, encapsulating the current price, confidence interval, +/// exponential moving average (EMA) price, and other relevant details. +/// +/// # Parameters +/// - `id`: The fixed byte identifier for the asset being updated. +/// - `price`: The current price of the asset as a 64-bit signed integer. +/// - `conf`: The confidence level of the current price as a 64-bit unsigned integer. +/// - `expo`: The exponent for the price, indicating its precision. +/// - `ema_price`: The Exponential Moving Average price of the asset as a 64-bit signed integer. +/// - `ema_conf`: The confidence level of the EMA price as a 64-bit unsigned integer. +/// - `publish_time`: The time the price was published, represented as a `U256`. +/// - `prev_publish_time`: The previous publish time of the price, represented as a 64-bit unsigned integer. +/// +/// # Returns +/// - `Vec`: A byte vector containing the encoded update data for the price feed. pub fn create_price_feed_update_data( - id:FixedBytes<32>, - price:i64, - conf:u64, - expo:i32, - ema_price:i64, - ema_conf:u64, - publish_time:U256, - prev_publish_time:u64 - ) -> Vec { - let price = Price { price: price, conf, expo, publish_time }; - let ema_price = Price { price:ema_price, conf:ema_conf, expo:expo,publish_time}; - - let price_feed_data = PriceFeed { - id,price,ema_price - }; - - let price_feed_data_encoding = (price_feed_data, prev_publish_time); - return DecodeDataType::abi_encode(&price_feed_data_encoding); - } \ No newline at end of file + id: FixedBytes<32>, + price: i64, + conf: u64, + expo: i32, + ema_price: i64, + ema_conf: u64, + publish_time: U256, + prev_publish_time: u64, +) -> Vec { + let price = Price { price, conf, expo, publish_time }; + let ema_price = Price { price: ema_price, conf: ema_conf, expo, publish_time }; + + let price_feed_data = PriceFeed { + id, + price, + ema_price, + }; + + let price_feed_data_encoding = (price_feed_data, prev_publish_time); + return DecodeDataType::abi_encode(&price_feed_data_encoding); +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index fee60885c9..a318d30b77 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -7,7 +7,7 @@ use crate::pyth::errors::{Error, PriceFeedNotFound}; use crate::pyth::types::{ PriceFeed,Price, StoragePriceFeed}; use crate::pyth::events::PriceFeedUpdate; -//decode data type PriceFeed and uint64 +///Decode data type PriceFeed and uint64 pub type DecodeDataType = (PriceFeed, SolUInt<64>); sol_storage! { diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index bda67f3836..8836d8dddf 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -1,8 +1,15 @@ mod errors; mod events; +/// Types and functions for interacting with the Pyth oracle. pub mod types; + +/// Mock module for testing pub mod mock; + +/// Functions for interacting with the Pyth oracle. pub mod functions; + +/// Conttract for interacting with the Pyth oracle. pub mod pyth_contract; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index 56ecb04264..e1955a771d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -7,26 +7,75 @@ use crate::pyth::functions::{ get_price_no_older_than, get_ema_price_unsafe, get_ema_price_no_older_than, + get_valid_time_period, update_price_feeds, update_price_feeds_if_necessary, get_update_fee, parse_price_feed_updates, parse_price_feed_updates_unique }; - +/// `IPyth` is a trait that defines methods for interacting with the Pyth contract. pub trait IPyth { + /// The Error Type for the Pyth Contract. + /// - `Vec`: The error message in bytes. type Error: Into>; + /// Retrieves the latest price feed without any recency checks. + /// + /// # Parameters + /// - `id`: The unique identifier for the price feed. + /// + /// # Returns + /// - `Result, Self::Error>`: The price data in bytes, or an error. fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; - fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: U256) -> Result, Self::Error>; - + /// Retrieves a price that is no older than a specified `age`. + /// + /// # Parameters + /// - `id`: The unique identifier for the price feed. + /// - `age`: The maximum acceptable age of the price in seconds. + /// + /// # Returns + /// - `Result, Self::Error>`: The price data in bytes, or an error. + fn get_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error>; + + /// Retrieves the exponentially-weighted moving average (EMA) price without recency checks. + /// + /// # Parameters + /// - `id`: The unique identifier for the price feed. + /// + /// # Returns + /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error>; - + /// Retrieves an EMA price that is no older than the specified `age`. + /// + /// # Parameters + /// - `id`: The unique identifier for the price feed. + /// - `age`: The maximum acceptable age of the price in seconds. + /// + /// # Returns + /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error>; + + /// Updates price feeds with the given data. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// + /// # Returns + /// - `Result<(), Self::Error>`: Success or error. fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + /// Updates price feeds if necessary, based on given publish times. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// - `price_ids`: Array of price IDs. + /// - `publish_times`: Array of publish times for the corresponding price IDs. + /// + /// # Returns + /// - `Result<(), Self::Error>`: Success or error. fn update_price_feeds_if_necessary( &mut self, update_data: Vec, @@ -34,8 +83,34 @@ pub trait IPyth { publish_times: Vec, ) -> Result<(), Self::Error>; + /// Returns the fee required to update the price feeds based on the provided data. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// + /// # Returns + /// - `Result`: The required fee in Wei, or an error. fn get_update_fee(&mut self, update_data: Vec) -> Result; + /// Returns the fee required to update the price feeds based on the provided data. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// + /// # Returns + /// - `Result`: The required fee in Wei, or an error. + fn get_valid_time_period(&mut self) -> Result; + + /// Parses the price feed updates for specific price IDs within a given time range. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// - `price_ids`: Array of price IDs to parse. + /// - `min_publish_time`: Minimum acceptable publish time for the price IDs. + /// - `max_publish_time`: Maximum acceptable publish time for the price IDs. + /// + /// # Returns + /// - `Result, Self::Error>`: Parsed price feed data in bytes, or an error. fn parse_price_feed_updates( &mut self, update_data: Vec, @@ -44,7 +119,16 @@ pub trait IPyth { max_publish_time: u64, ) -> Result, Self::Error>; - + /// Parses price feed updates for specific price IDs, ensuring only the first updates within a time range are returned. + /// + /// # Parameters + /// - `update_data`: Array of price update data. + /// - `price_ids`: Array of price IDs to parse. + /// - `min_publish_time`: Minimum acceptable publish time for the price IDs. + /// - `max_publish_time`: Maximum acceptable publish time for the price IDs. + /// + /// # Returns + /// - `Result, Self::Error>`: Parsed price feed data in bytes, or an error. fn parse_price_feed_updates_unique( &mut self, update_data: Vec, @@ -52,14 +136,14 @@ pub trait IPyth { min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error>; - - } - sol_storage! { + /// `PythContract` represents the contract that interacts with the Pyth oracle. + /// This struct contains only the address of the Pyth contract. pub struct PythContract { - address _ipyth; + /// `_ipyth` is the address of the deployed Pyth contract. + address _ipyth; } } @@ -88,12 +172,17 @@ impl IPyth for PythContract { Ok(data) } - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: u8) -> Result, Self::Error> { - let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,U256::from(age))?; + fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error> { + let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,age)?; let data = price.abi_encode(); Ok(data) } + fn get_valid_time_period(&mut self) -> Result { + let time = get_valid_time_period(self, self._ipyth.get())?; + Ok(time) + } + fn get_update_fee(&mut self, update_data: Vec) -> Result { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); let fee = get_update_fee(self, self._ipyth.get(), data)?; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index cad896107f..75b5ea238a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -1,27 +1,12 @@ -//This Module implements StoragePrice and StoragePriceFeed Structs. -// -// These structs represent price and price feed data in as recived fron pyth sdk . -// -// `StoragePrice` is used to store the essential details of a price: the value, confidence interval, exponent, and publish time. -// -// `StoragePriceFeed` represents a complete price feed that includes a unique identifier (ID) and two price structures: the current price and the exponentially moving average (EMA) price. -// -// Note that these structures are not functional on their own. You need to implement additional logic to properly interact with the price feed data in your contracts. -// -// Key Features: -// - `StoragePrice`: stores a single price and its metadata. -// - `StoragePriceFeed`: manages a feed of price data, with both current and EMA prices. -// -// Methods: -// - `to_price`: Converts a `StoragePrice` to a more generic `Price` structure. -// - `set`: Sets the values of `StoragePrice` or `StoragePriceFeed` using the provided `Price` or `PriceFeed` data. -// -// These methods ensure data consistency between the storage-optimized structs and the higher-level structures that will be used in external functions. +#![allow(missing_docs)] +//! Solidity type definitions used throughout the project use alloy_primitives::{ I32, I64, U64}; use stylus_sdk::{alloy_sol_types::sol, prelude::*}; + sol_storage !{ + /// Represents a storage-optimized price structure containing the current price data. pub struct StoragePrice { int64 price; uint64 conf; @@ -29,6 +14,7 @@ sol_storage !{ uint publish_time; } + /// Represents a storage-optimized price feed structure containing an ID and associated price data. pub struct StoragePriceFeed { bytes32 id; StoragePrice price; @@ -68,56 +54,56 @@ sol! { Price price; Price ema_price; } - // Function call selector: Fetches the price associated with the given ID without validation. - // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + /// Function call selector: Fetches the price associated with the given ID without validation. + /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. function getPriceUnsafe(bytes32 id) external view returns (int64,uint64,int32,uint); - // Function call selector: Retrieves the price associated with the given ID - // ensuring that the price is not older than the specified age in seconds. - // Returns the `Price` data for the specified `bytes32` ID. + /// Function call selector: Retrieves the price associated with the given ID + /// ensuring that the price is not older than the specified age in seconds. + /// Returns the `Price` data for the specified `bytes32` ID. function getPriceNoOlderThan( bytes32 id, uint age ) external view returns (Price memory price); - // Function call selector: Fetches the Exponential Moving Average (EMA) price - // associated with the given ID without validation. - // Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + /// Function call selector: Fetches the Exponential Moving Average (EMA) price + /// associated with the given ID without validation. + /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. function getEmaPriceUnsafe( bytes32 id ) external view returns (Price memory price); - // Function call selector: Retrieves the EMA price associated with the given ID - // ensuring that the price is not older than the specified age in seconds. - // Returns the `Price` data for the specified `bytes32` ID. + /// Function call selector: Retrieves the EMA price associated with the given ID + /// ensuring that the price is not older than the specified age in seconds. + /// Returns the `Price` data for the specified `bytes32` ID. function getEmaPriceNoOlderThan( bytes32 id, uint age ) external view returns (Price memory price); - // Function call selector: Updates multiple price feeds using the provided update data. - // Accepts an array of `bytes` containing update data and processes the updates. + /// Function call selector: Updates multiple price feeds using the provided update data. + /// Accepts an array of `bytes` containing update data and processes the updates. function updatePriceFeeds(bytes[] calldata updateData) external payable; - // Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. - // Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds - // and processes the updates if conditions are met. + /// Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. + /// Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds + /// and processes the updates if conditions are met. function updatePriceFeedsIfNecessary( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes ) external payable; - // Function call selector: Calculates the fee amount required to update price feeds - // based on the provided update data. - // Returns the fee amount as a `uint`. + /// Function call selector: Calculates the fee amount required to update price feeds + /// based on the provided update data. + /// Returns the fee amount as a `uint`. function getUpdateFee( bytes[] calldata updateData ) external view returns (uint feeAmount); - // Function call selector: Parses updates from the provided data for multiple price feeds. - // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, - // and returns an array of `PriceFeed` structs containing the parsed updates. + /// Function call selector: Parses updates from the provided data for multiple price feeds. + /// Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + /// and returns an array of `PriceFeed` structs containing the parsed updates. function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, @@ -125,9 +111,9 @@ sol! { uint64 maxPublishTime ) external payable returns (PriceFeed[] memory priceFeeds); - // Function call selector: Parses updates from the provided data for unique price feeds. - // Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, - // and returns an array of unique `PriceFeed` structs containing the parsed updates. + /// Function call selector: Parses updates from the provided data for unique price feeds. + /// Accepts arrays of `bytes` and `bytes32`, along with publish time constraints, + /// and returns an array of unique `PriceFeed` structs containing the parsed updates. function parsePriceFeedUpdatesUnique( bytes[] calldata updateData, bytes32[] calldata priceIds, @@ -135,20 +121,20 @@ sol! { uint64 maxPublishTime ) external payable returns (PriceFeed[] memory priceFeeds); - // Function call selector: Queries the price feed for a given ID. - // Returns an array of `PriceFeed` structs associated with the specified `bytes32` ID. + /// Function call selector: Queries the price feed for a given ID. + /// Returns an array of `PriceFeed` structs associated with the specified `bytes32` ID. function queryPriceFeed( bytes32 id ) public view virtual returns (PriceFeed[] memory priceFeeds); - // Function call selector: Checks if a price feed exists for the given ID. - // Returns an array of `PriceFeed` structs if it exists for the specified `bytes32` ID. + /// Function call selector: Checks if a price feed exists for the given ID. + /// Returns an array of `PriceFeed` structs if it exists for the specified `bytes32` ID. function priceFeedExists( bytes32 id ) public view virtual returns (PriceFeed[] memory priceFeeds); - // Function call selector: Retrieves the valid time period for price feeds. - // Returns the valid time period as a `uint`. + /// Function call selector: Retrieves the valid time period for price feeds. + /// Returns the valid time period as a `uint`. function getValidTimePeriod() public view @@ -156,12 +142,13 @@ sol! { returns (uint validTimePeriod); } + impl StoragePrice { - // Converts the `StoragePrice` instance into a `Price` struct. - // - // This method retrieves the stored values from the `StoragePrice` - // and creates a `Price` struct, making it suitable for external - // use or return types in function calls. + /// Converts the `StoragePrice` instance into a `Price` struct. + /// + /// This method retrieves the stored values from the `StoragePrice` + /// and creates a `Price` struct, making it suitable for external + /// use or return types in function calls. pub fn to_price(&self) -> Price { Price { price: self.price.get().as_i64(), @@ -171,10 +158,10 @@ impl StoragePrice { } } - // Sets the values of the `StoragePrice` instance from a given `Price` struct. - // This method updates the stored values in the `StoragePrice` with - // the corresponding values from the provided `Price` struct, ensuring - // that the internal state is accurately reflected. + /// Sets the values of the `StoragePrice` instance from a given `Price` struct. + /// This method updates the stored values in the `StoragePrice` with + /// the corresponding values from the provided `Price` struct, ensuring + /// that the internal state is accurately reflected. pub fn set(&mut self, price: Price) { self.price.set(I64::try_from(price.price).unwrap()); self.conf.set(U64::try_from(price.conf).unwrap()); @@ -184,12 +171,12 @@ impl StoragePrice { } impl StoragePriceFeed { - // Converts the `StoragePriceFeed` instance into a `PriceFeed` struct. - // - // This method retrieves the stored price feed values and creates - // a `PriceFeed` struct, which includes the unique identifier and - // associated price data, making it suitable for external use or - // return types in function calls. + /// Converts the `StoragePriceFeed` instance into a `PriceFeed` struct. + /// + /// This method retrieves the stored price feed values and creates + /// a `PriceFeed` struct, which includes the unique identifier and + /// associated price data, making it suitable for external use or + /// return types in function calls. pub fn to_price_feed(&self) -> PriceFeed { PriceFeed { id: self.id.get(), @@ -198,11 +185,11 @@ impl StoragePriceFeed { } } - // Sets the values of the `StoragePriceFeed` instance from a given `PriceFeed` struct. - // - // This method updates the stored values in the `StoragePriceFeed` - // with the corresponding values from the provided `PriceFeed` struct, - // ensuring that the internal state is accurately reflected. + /// Sets the values of the `StoragePriceFeed` instance from a given `PriceFeed` struct. + /// + /// This method updates the stored values in the `StoragePriceFeed` + /// with the corresponding values from the provided `PriceFeed` struct, + /// ensuring that the internal state is accurately reflected. pub fn set(&mut self, price_feed: PriceFeed) { self.id.set(price_feed.id); self.price.set(price_feed.price); diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs index 1630fabcd1..bf2c2d089a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs @@ -1 +1,2 @@ +/// Utility functions for interacting with the Pyth oracle. pub mod helpers; From 8464896c58dbb79c4356562c1f1777a2668e8376 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 07:37:05 +0000 Subject: [PATCH 068/160] chore:function calls --- .../stylus/examples/function-calls/src/lib.rs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 2e2aa97613..04fe13699d 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -1,18 +1,12 @@ #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; +use alloc::vec; use alloc::vec::Vec; -use alloy_primitives::U256; -use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage, SolidityError}}; +use alloy_primitives::{Bytes, U256}; +use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage, SolidityError}}; use pyth_stylus::pyth::{functions::{ - get_price_no_older_than, - get_ema_price_no_older_than, - get_ema_price_unsafe, - get_price_unsafe, - get_update_fee, - get_valid_time_period, - update_price_feeds, - update_price_feeds_if_necessary + create_price_feed_update_data, get_ema_price_no_older_than, get_ema_price_unsafe, get_price_no_older_than, get_price_unsafe, get_update_fee, get_valid_time_period, update_price_feeds, update_price_feeds_if_necessary },types::{ StoragePrice, StoragePriceFeed @@ -75,9 +69,11 @@ impl FunctionCallsExample { Ok(()) } - pub fn get_update_fee(&mut self) -> Result<(), Vec> { - let _ = get_update_fee(self, self.pyth_address.get(), Vec::new())?; - Ok(()) + pub fn get_update_fee(&mut self) -> Result> { + let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); + let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); + let fee = get_update_fee(self, self.pyth_address.get(), data_bytes)?; + Ok(fee) } pub fn get_valid_time_period(&mut self) -> Result<(), Vec> { @@ -87,8 +83,19 @@ impl FunctionCallsExample { #[payable] pub fn update_price_feeds(&mut self) -> Result<(), Vec> { - let _ = update_price_feeds(self, self.pyth_address.get(), Vec::new())?; + let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); + let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); + let _ = update_price_feeds(self, self.pyth_address.get(), data_bytes)?; + Ok(()) + } + + #[payable] + pub fn update_price_feeds_if_necessary(&mut self) -> Result<(), Vec> { + let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); + let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); + let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data_bytes, vec![self.price_id.get()],vec![0])?; Ok(()) } + } \ No newline at end of file From 196018c24b004db0efeede558c823f49448dd343 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 07:41:46 +0000 Subject: [PATCH 069/160] chore: Ignore all broadcast --- target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore b/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore index 85198aaa55..bb98a38c47 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore @@ -4,6 +4,7 @@ out/ # Ignores development broadcast logs !/broadcast +/broadcast/* /broadcast/*/31337/ /broadcast/**/dry-run/ From 072ec4ea8584e083935736c3c3c86f8b26e298bf Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 14:14:35 +0000 Subject: [PATCH 070/160] chore: completed get benches --- target_chains/ethereum/sdk/stylus/Cargo.lock | 1 + .../ethereum/sdk/stylus/benches/Cargo.toml | 2 +- .../sdk/stylus/benches/src/proxy_calls.rs | 8 ++-- .../ethereum/sdk/stylus/contracts/Cargo.toml | 1 + .../sdk/stylus/contracts/src/pyth/mock.rs | 31 ++++++++++++---- .../stylus/examples/function-calls/Cargo.toml | 1 + .../stylus/examples/function-calls/src/lib.rs | 37 +++++++++---------- .../stylus/examples/proxy-calls/src/lib.rs | 12 +----- 8 files changed, 50 insertions(+), 43 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 2568b0cfd3..44a77c487a 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2726,6 +2726,7 @@ dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "alloy-sol-types", + "keccak-const", "mini-alloc", "motsu", "stylus-sdk", diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml index f5ed28e161..7389728f7b 100644 --- a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -16,5 +16,5 @@ futures.workspace = true eyre.workspace = true koba.workspace = true e2e.workspace = true +keccak-const.workspace = true serde = "1.0.203" -keccak-const = "0.2.0" \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 9f7f0c1bfb..b068c4012a 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -6,7 +6,7 @@ use alloy::{ sol, sol_types::{ SolCall, SolConstructor} }; use e2e::{receipt, Account}; -use pyth_stylus::pyth::functions::create_price_feed_update_data; +use pyth_stylus::pyth::mock::create_price_feed_update_data_list; use crate::{ env, report::{ContractReport, FunctionReport}, CacheOpt @@ -58,14 +58,14 @@ pub async fn run_with( let time_frame = uint!(10000_U256); let age = uint!(10000_U256); - let data = create_price_feed_update_data(id, 10, 100, 100, 100, 100, U256::from(100), 0); - let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); + let (data, _ids) = create_price_feed_update_data_list(); let _ = receipt!(contract.getPriceUnsafe(id))?; let _ = receipt!(contract.getEmaPriceUnsafe(id))?; let _ = receipt!(contract.getPriceNoOlderThan(id,age))?; let _ = receipt!(contract.getEmaPriceNoOlderThan(id,age))?; let _ = receipt!(contract.getValidTimePeriod())?; + let _ = receipt!(contract.getUpdateFee(data.clone()))?; // IMPORTANT: Order matters! use ProxyCall::*; @@ -76,7 +76,7 @@ pub async fn run_with( (getPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getPriceNoOlderThan(id, time_frame))?), (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan(id, time_frame))?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), - (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee(data_bytes.clone()))?), + (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee(data.clone()))?), ]; receipts diff --git a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml index dcd4d272ef..1183eb7cf7 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/contracts/Cargo.toml @@ -14,6 +14,7 @@ alloy-sol-macro-expander.workspace = true alloy-sol-macro-input.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true +keccak-const.workspace = true [dev-dependencies] motsu.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index a318d30b77..4f7a575167 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -1,11 +1,12 @@ use alloc::vec::Vec; -use alloy_primitives::Uint; +use alloy_primitives::{Uint, FixedBytes, U256, Bytes}; use alloy_sol_types::{ sol_data::Uint as SolUInt, SolType, SolValue}; -use stylus_sdk::{abi::Bytes, alloy_primitives::{FixedBytes, U256}, evm, msg, prelude::*}; +use stylus_sdk::{abi::Bytes as AbiBytes,evm, msg, prelude::*}; use crate::{pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE}; use crate::pyth::errors::{Error, PriceFeedNotFound}; use crate::pyth::types::{ PriceFeed,Price, StoragePriceFeed}; use crate::pyth::events::PriceFeedUpdate; +use crate::pyth::functions::create_price_feed_update_data; ///Decode data type PriceFeed and uint64 pub type DecodeDataType = (PriceFeed, SolUInt<64>); @@ -65,7 +66,7 @@ impl MockPythContract { #[payable] fn update_price_feeds(&mut self, - update_data : Vec + update_data : Vec ) -> Result<(), Vec> { let required_fee = self.get_update_fee(update_data.clone()); if required_fee.lt(&U256::from(msg::value())) { @@ -89,13 +90,13 @@ impl MockPythContract { Ok(()) } - fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { + fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { self.single_update_fee_in_wei.get() * U256::from(update_data.len()) } #[payable] fn parse_price_feed_updates( &mut self, - update_data:Vec, + update_data:Vec, price_ids:Vec>, min_publish_time:u64, max_publish_time:u64 @@ -106,7 +107,7 @@ impl MockPythContract { #[payable] fn parse_price_feed_updates_unique( &mut self, - update_data:Vec, + update_data:Vec, price_ids:Vec>, min_publish_time:u64, max_publish_time:u64 @@ -139,7 +140,7 @@ impl MockPythContract { impl MockPythContract { fn parse_price_feed_updates_internal(&mut self, - update_data: Vec, + update_data: Vec, price_ids: Vec>, min_publish_time:u64, max_publish_time:u64, @@ -196,6 +197,22 @@ impl MockPythContract { } } +pub fn create_price_feed_update_data_list() -> (Vec, Vec>) { + let id = ["ETH","SOL","BTC"].map(|x| { + let x = keccak_const::Keccak256::new().update(x.as_bytes()).finalize().to_vec(); + return FixedBytes::<32>::from_slice(&x) ; + }); + let mut price_feed_data_list = Vec::new(); + for i in 0..3 { + let price_feed_data = create_price_feed_update_data( id[i],100,100,100,100,100,U256::from(100),0); + let price_feed_data = Bytes::from(AbiBytes::from(price_feed_data).0); + price_feed_data_list.push(price_feed_data); + } + return (price_feed_data_list, id.to_vec()) +} + + + #[cfg(all(test, feature = "std"))] mod tests { diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index ccae44a760..a5a32566f4 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -13,6 +13,7 @@ alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true + [dev-dependencies] alloy.workspace = true eyre.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 04fe13699d..22ff137ec5 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -3,13 +3,15 @@ extern crate alloc; use alloc::vec; use alloc::vec::Vec; -use alloy_primitives::{Bytes, U256}; +use alloy_primitives::U256; use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage, SolidityError}}; -use pyth_stylus::pyth::{functions::{ - create_price_feed_update_data, get_ema_price_no_older_than, get_ema_price_unsafe, get_price_no_older_than, get_price_unsafe, get_update_fee, get_valid_time_period, update_price_feeds, update_price_feeds_if_necessary -},types::{ - StoragePrice, - StoragePriceFeed +use pyth_stylus::pyth::{ + mock::create_price_feed_update_data_list, + functions::{ + get_ema_price_no_older_than, get_ema_price_unsafe, get_price_no_older_than, get_price_unsafe, get_update_fee, get_valid_time_period, update_price_feeds, update_price_feeds_if_necessary + },types::{ + StoragePrice, + StoragePriceFeed }}; use alloy_sol_types::sol; @@ -70,10 +72,9 @@ impl FunctionCallsExample { } pub fn get_update_fee(&mut self) -> Result> { - let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); - let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); - let fee = get_update_fee(self, self.pyth_address.get(), data_bytes)?; - Ok(fee) + let (data,_) = create_price_feed_update_data_list(); + let fee = get_update_fee(self, self.pyth_address.get(), data)?; + Ok(fee) } pub fn get_valid_time_period(&mut self) -> Result<(), Vec> { @@ -83,19 +84,15 @@ impl FunctionCallsExample { #[payable] pub fn update_price_feeds(&mut self) -> Result<(), Vec> { - let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); - let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); - let _ = update_price_feeds(self, self.pyth_address.get(), data_bytes)?; - Ok(()) + let (data_bytes, _) = create_price_feed_update_data_list(); + let _ = update_price_feeds(self, self.pyth_address.get(), data_bytes)?; + Ok(()) } #[payable] pub fn update_price_feeds_if_necessary(&mut self) -> Result<(), Vec> { - let data = create_price_feed_update_data(self.price_id.get(), 10, 100, 100, 100, 100, U256::from(100), 0); - let data_bytes: Vec = data.iter().map(|&x| {Bytes::from(vec![x]) }).collect(); - let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data_bytes, vec![self.price_id.get()],vec![0])?; + let (data,ids) = create_price_feed_update_data_list(); + let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data, ids,vec![0])?; Ok(()) } - - -} \ No newline at end of file +} diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs index 09876ff5fb..5e211716ca 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs @@ -2,13 +2,8 @@ extern crate alloc; -use alloc::vec::Vec; -use alloy_primitives::{ FixedBytes, U256}; use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; -use pyth_stylus::{pyth::{ - pyth_contract::{IPyth, PythContract}, - types::Price -}, utils::helpers::decode_helper}; +use pyth_stylus::pyth::pyth_contract::PythContract; sol_storage! { @@ -22,9 +17,4 @@ sol_storage! { #[public] #[inherit(PythContract)] impl ProxyCallsExample { - pub fn get_price_no_older_than(&mut self,id : FixedBytes<32>, age:U256) -> Result<(), Vec> { - let price = self.pyth.get_price_no_older_than(id, age)?; - let _ = decode_helper::(&price)?; - Ok(()) - } } \ No newline at end of file From 70432b7612b9cccd3dffefcd37c71802181a46a3 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 3 Nov 2024 16:23:17 +0000 Subject: [PATCH 071/160] chore:changes --- .../ethereum/sdk/stylus/benches/Cargo.toml | 1 + .../sdk/stylus/benches/src/function_calls.rs | 4 ++++ .../ethereum/sdk/stylus/benches/src/proxy_calls.rs | 13 +++++++++++-- .../ethereum/sdk/stylus/contracts/src/pyth/mock.rs | 5 +---- .../sdk/stylus/contracts/src/pyth/pyth_contract.rs | 6 ++++-- .../sdk/stylus/examples/function-calls/src/lib.rs | 2 +- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml index 7389728f7b..8bfe677975 100644 --- a/target_chains/ethereum/sdk/stylus/benches/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/benches/Cargo.toml @@ -13,6 +13,7 @@ alloy-primitives = { workspace = true, features = ["tiny-keccak"] } alloy.workspace = true tokio.workspace = true futures.workspace = true +stylus-sdk.workspace = true eyre.workspace = true koba.workspace = true e2e.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 7c31b67ee2..ca8d64397a 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -61,6 +61,8 @@ pub async fn run_with( let _ = receipt!(contract.getEmaPriceNoOlderThan())?; let _ = receipt!(contract.getUpdateFee())?; let _ = receipt!(contract.getValidTimePeriod())?; + // let _ = receipt!(contract.updatePriceFeeds())?; + // let _ = receipt!(contract.updatePriceFeedsIfNecessary())?; // IMPORTANT: Order matters! use FunctionCall::*; @@ -72,6 +74,8 @@ pub async fn run_with( (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan())?), (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee())?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), + //(updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds())?), + //(updatePriceFeedsIfNecessaryCall::SIGNATURE, receipt!(contract.updatePriceFeedsIfNecessary())?) ]; receipts diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index b068c4012a..f391b807e7 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{i64, str::FromStr}; use alloy::{ network::{AnyNetwork, EthereumWallet}, primitives::{uint, Address, FixedBytes as TypeFixedBytes, Bytes, U256}, @@ -21,6 +21,8 @@ sol!( function getEmaPriceNoOlderThan(bytes32 id, uint age) external; function getUpdateFee(bytes[] calldata updateData) external returns (uint256); function getValidTimePeriod() external; + function updatePriceFeeds(bytes[] calldata updateData) external payable; + function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; } ); @@ -58,7 +60,8 @@ pub async fn run_with( let time_frame = uint!(10000_U256); let age = uint!(10000_U256); - let (data, _ids) = create_price_feed_update_data_list(); + let (data, ids) = create_price_feed_update_data_list(); + let _ = receipt!(contract.getPriceUnsafe(id))?; let _ = receipt!(contract.getEmaPriceUnsafe(id))?; @@ -66,6 +69,10 @@ pub async fn run_with( let _ = receipt!(contract.getEmaPriceNoOlderThan(id,age))?; let _ = receipt!(contract.getValidTimePeriod())?; let _ = receipt!(contract.getUpdateFee(data.clone()))?; + let _ = receipt!(contract.updatePriceFeeds(data.clone()))?; + + //println!("{data:?} , {ids:?} "); + // let _ = receipt!(contract.updatePriceFeedsIfNecessary(data.clone(),ids.clone(), vec![i64::MAX,i64::MAX,i64::MAX]))?; // IMPORTANT: Order matters! use ProxyCall::*; @@ -77,6 +84,8 @@ pub async fn run_with( (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan(id, time_frame))?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee(data.clone()))?), + //(updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds(data.clone()))?), + //(updatePriceFeedsIfNecessaryCall::SIGNATURE, receipt!(contract.updatePriceFeedsIfNecessary(data.clone(), ids.clone(), vec![0,0,0]))?) ]; receipts diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 4f7a575167..5e5c22044e 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -204,16 +204,13 @@ pub fn create_price_feed_update_data_list() -> (Vec, Vec>) }); let mut price_feed_data_list = Vec::new(); for i in 0..3 { - let price_feed_data = create_price_feed_update_data( id[i],100,100,100,100,100,U256::from(100),0); + let price_feed_data = create_price_feed_update_data( id[i],100,100,100,100,100,U256::from(U256::MAX - U256::from(10)),0); let price_feed_data = Bytes::from(AbiBytes::from(price_feed_data).0); price_feed_data_list.push(price_feed_data); } return (price_feed_data_list, id.to_vec()) } - - - #[cfg(all(test, feature = "std"))] mod tests { use alloc::vec; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index e1955a771d..6757f75524 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -192,7 +192,8 @@ impl IPyth for PythContract { #[payable] fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds(self, self._ipyth.get(),data) + update_price_feeds(self, self._ipyth.get(),data)?; + Ok(()) } #[payable] @@ -203,7 +204,8 @@ impl IPyth for PythContract { publish_times: Vec, ) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times) + update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times)?; + Ok(()) } #[payable] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 22ff137ec5..18cd3a53e0 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -92,7 +92,7 @@ impl FunctionCallsExample { #[payable] pub fn update_price_feeds_if_necessary(&mut self) -> Result<(), Vec> { let (data,ids) = create_price_feed_update_data_list(); - let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data, ids,vec![0])?; + let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data, ids,vec![0, 0, 0])?; Ok(()) } } From cd728b61880da76624f73dfd96e5e1fdebac7fef Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 4 Nov 2024 08:02:43 +0000 Subject: [PATCH 072/160] chore: Added Type Test --- .../sdk/stylus/contracts/src/pyth/mock.rs | 5 +- .../sdk/stylus/contracts/src/pyth/types.rs | 89 ++++++++++++++++++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 5e5c22044e..550b66347b 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -216,8 +216,9 @@ mod tests { use alloc::vec; use alloy_primitives::{address, uint, Address, U64, U256, FixedBytes, fixed_bytes}; use stylus_sdk::{abi::Bytes, contract, msg::{self, value}}; - use crate::pyth::{ - PythContract, mock::{MockPythContract, DecodeDataType}, + use + crate::pyth::{ + mock::{MockPythContract, DecodeDataType}, errors::{Error, InvalidArgument}, types::{PriceFeed, Price, StoragePriceFeed} }; use alloy_sol_types::SolType; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 75b5ea238a..2a770bb55c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] //! Solidity type definitions used throughout the project -use alloy_primitives::{ I32, I64, U64}; +use alloy_primitives::{ I32, I64, U64, U256}; use stylus_sdk::{alloy_sol_types::sol, prelude::*}; @@ -142,7 +142,6 @@ sol! { returns (uint validTimePeriod); } - impl StoragePrice { /// Converts the `StoragePrice` instance into a `Price` struct. /// @@ -168,6 +167,13 @@ impl StoragePrice { self.expo.set(I32::try_from(price.expo).unwrap()); self.publish_time.set(price.publish_time); } + + /// This function is for just for testing + pub fn from_price(price: Price) -> Self { + let mut storage_price = unsafe { StoragePrice::new(U256::from(100), 0)}; + storage_price.set(price); + storage_price + } } impl StoragePriceFeed { @@ -195,5 +201,84 @@ impl StoragePriceFeed { self.price.set(price_feed.price); self.ema_price.set(price_feed.ema_price); } + + pub fn from_price_feed(price_feed: PriceFeed) -> Self { + let mut storage_price_feed = unsafe { StoragePriceFeed::new(U256::from(100), 0)}; + storage_price_feed.set(price_feed); + storage_price_feed + } + } + + +#[cfg(all(test, feature = "std"))] +mod tests { + use alloy_primitives::{address, FixedBytes, U256}; + use crate::pyth::types::{PriceFeed, Price, StoragePriceFeed, StoragePrice}; + + // Updated constants to use uppercase naming convention + const PRICE: i64 = 1000; + const CONF: u64 = 1000; + const EXPO: i32 = 1000; + const EMA_PRICE: i64 = 1000; + const EMA_CONF: u64 = 1000; + + fn generate_bytes() -> FixedBytes<32> { + FixedBytes::<32>::repeat_byte(30) + } + + #[motsu::test] + fn can_create_type_price() { + let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + assert_eq!(price_result.price, PRICE); + assert_eq!(price_result.conf, CONF); + assert_eq!(price_result.expo, EXPO); + assert_eq!(price_result.publish_time, U256::from(1000)); + } + + #[motsu::test] + fn can_create_type_price_feed() { + let id = generate_bytes(); + let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_result_ema = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_feed_result = PriceFeed{ id, price: price_result, ema_price: price_result_ema}; + assert_eq!(price_feed_result.price.price, PRICE); + assert_eq!(price_feed_result.price.conf, CONF); + assert_eq!(price_feed_result.price.expo, EXPO); + assert_eq!(price_feed_result.price.publish_time, U256::from(1000)); + assert_eq!(price_feed_result.ema_price.price, PRICE); + assert_eq!(price_feed_result.ema_price.conf, CONF); + assert_eq!(price_feed_result.ema_price.expo, EXPO); + assert_eq!(price_feed_result.ema_price.publish_time, U256::from(1000)); + } + + #[motsu::test] + fn can_create_type_storage_price() { + let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let storage_price_result = StoragePrice::from_price(price_result); + let price_result = storage_price_result.to_price(); + assert_eq!(price_result.price, PRICE); + assert_eq!(price_result.conf, CONF); + assert_eq!(price_result.expo, EXPO); + assert_eq!(price_result.publish_time, U256::from(1000)); + } + + #[motsu::test] + fn can_create_type_storage_price_feed() { + let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_result_ema = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_feed_result = PriceFeed{ id: generate_bytes(), price: price_result, ema_price: price_result_ema}; + let storage_price_feed_result = StoragePriceFeed::from_price_feed(price_feed_result); + let price_feed_result = storage_price_feed_result.to_price_feed(); + assert_eq!(price_feed_result.price.price, PRICE); + assert_eq!(price_feed_result.price.conf, CONF); + assert_eq!(price_feed_result.price.expo, EXPO); + assert_eq!(price_feed_result.price.publish_time, U256::from(1000)); + assert_eq!(price_feed_result.ema_price.price, PRICE); + assert_eq!(price_feed_result.ema_price.conf, CONF); + assert_eq!(price_feed_result.ema_price.expo, EXPO); + assert_eq!(price_feed_result.ema_price.publish_time, U256::from(1000)); + } + +} \ No newline at end of file From 30c5056366d86d5baa451b6ae222912af5e1623b Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 5 Nov 2024 09:06:22 +0000 Subject: [PATCH 073/160] chore: tests --- target_chains/ethereum/sdk/stylus/Cargo.lock | 1 + .../stylus/examples/function-calls/src/lib.rs | 1 + .../examples/function-calls/tests/abi/mod.rs | 66 +++---------------- .../function-calls/tests/function-calls.rs | 31 ++++++--- .../examples/proxy-calls/tests/proxy-calls.rs | 16 ++--- 5 files changed, 42 insertions(+), 73 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 44a77c487a..fedc1e57fc 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -791,6 +791,7 @@ dependencies = [ "koba", "pyth-stylus", "serde", + "stylus-sdk", "tokio", ] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 18cd3a53e0..f5a0afc5d0 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -51,6 +51,7 @@ pub enum MultiCallErrors { impl FunctionCallsExample { pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { let price = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; + self.price.set(price); if price.price > 0 { return Ok(()); } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs index 34ec44e34a..fc8202dcae 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs @@ -3,62 +3,14 @@ use alloy::sol; sol!( #[sol(rpc)] - contract Pyth { - function approve(address to, uint256 tokenId) external; - #[derive(Debug)] - function balanceOf(address owner) external view returns (uint256 balance); - #[derive(Debug)] - function getApproved(uint256 tokenId) external view returns (address approved); - #[derive(Debug)] - function isApprovedForAll(address owner, address operator) external view returns (bool approved); - #[derive(Debug)] - function ownerOf(uint256 tokenId) external view returns (address ownerOf); - function safeTransferFrom(address from, address to, uint256 tokenId) external; - function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; - function setApprovalForAll(address operator, bool approved) external; - function totalSupply() external view returns (uint256 totalSupply); - function transferFrom(address from, address to, uint256 tokenId) external; - - function mint(address to, uint256 tokenId) external; - function burn(uint256 tokenId) external; - - function paused() external view returns (bool paused); - function pause() external; - function unpause() external; - #[derive(Debug)] - function whenPaused() external view; - #[derive(Debug)] - function whenNotPaused() external view; - - #[derive(Debug)] - function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); - #[derive(Debug)] - function tokenByIndex(uint256 index) external view returns (uint256 tokenId); - - function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); - - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); - error ERC721InsufficientApproval(address operator, uint256 tokenId); - error ERC721InvalidApprover(address approver); - error ERC721InvalidOperator(address operator); - error ERC721InvalidOwner(address owner); - error ERC721InvalidReceiver(address receiver); - error ERC721InvalidSender(address sender); - error ERC721NonexistentToken(uint256 tokenId); - error ERC721OutOfBoundsIndex(address owner, uint256 index); - error ERC721EnumerableForbiddenBatchMint(); - error EnforcedPause(); - error ExpectedPause(); - - #[derive(Debug, PartialEq)] - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - #[derive(Debug, PartialEq)] - event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - #[derive(Debug, PartialEq)] - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - #[derive(Debug, PartialEq)] - event Paused(address account); - #[derive(Debug, PartialEq)] - event Unpaused(address account); + contract FunctionCalls { + function getPriceUnsafe() external ; + function getEmaPriceUnsafe() external ; + function getPriceNoOlderThan() external ; + function getEmaPriceNoOlderThan() external; + function getUpdateFee() external; + function getValidTimePeriod() external; + function updatePriceFeeds() external payable; + function updatePriceFeedsIfNecessary() external payable; } ); \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index acad6a37ad..c9d874fbde 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -1,20 +1,35 @@ #![cfg(feature = "e2e")] -use alloy::hex; -use e2e::{receipt,ReceiptExt,Account}; +use abi::FunctionCalls; +use alloy::{ + primitives::{uint, Address, U256}, + sol, +}; +use e2e::{ + receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, + Revert, +}; use eyre::Result; +// use crate::FunctionCallsExample::constructorCall; + mod abi; +sol!("src/constructor.sol"); // // ============================================================================ // // Integration Tests: Function Calls // // ============================================================================ - #[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - let contract_addr = alice.as_deployer().deploy().await?.address()?; -// // assert_eq!(true, true); +// #[e2e::test] +// async fn constructs(alice: Account) -> Result<()> { +// let contract_addr = alice +// .as_deployer() +// .with_default_constructor::() +// .deploy() +// .await? +// .address()?; +// // // assert_eq!(true, true); - Ok(()) - } +// Ok(()) +// } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs index d56528ce41..57bba732dc 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs @@ -1,20 +1,20 @@ #![cfg(feature = "e2e")] -use alloy::hex; +use alloy::{hex, sol}; use e2e::{receipt,ReceiptExt,Account}; use eyre::Result; mod abi; - +sol!("src/constructor.sol"); // // ============================================================================ // // Integration Tests: Proxy Calls // // ============================================================================ - #[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - // let contract_addr = alice.as_deployer().deploy().await?.address()?; -// // assert_eq!(true, true); +// #[e2e::test] +// async fn constructs(alice: Account) -> Result<()> { +// // // let contract_addr = alice.as_deployer().deploy().await?.address()?; +// // // // assert_eq!(true, true); - Ok(()) - } +// Ok(()) +// } From 908b866e6fa24b4fad4b508a049fc671a957e768 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 5 Nov 2024 20:55:56 +0000 Subject: [PATCH 074/160] env and examples --- target_chains/ethereum/sdk/stylus/Cargo.lock | 1 + .../sdk/stylus/benches/src/function_calls.rs | 6 +- .../sdk/stylus/benches/src/proxy_calls.rs | 4 +- .../stylus/examples/function-calls/Cargo.toml | 4 +- .../function-calls/tests/function-calls.rs | 81 ++++++++++++++----- .../examples/proxy-calls/tests/abi/mod.rs | 66 +++------------ .../ethereum/sdk/stylus/lib/e2e/src/lib.rs | 7 ++ 7 files changed, 84 insertions(+), 85 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index fedc1e57fc..e52fabf510 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -1628,6 +1628,7 @@ dependencies = [ "alloy-sol-types", "e2e", "eyre", + "keccak-const", "mini-alloc", "pyth-stylus", "stylus-sdk", diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index ca8d64397a..fcc1d56a8f 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -7,11 +7,9 @@ use alloy::{ sol, sol_types::{SolCall, SolConstructor}, }; -use e2e::{receipt, Account}; +use e2e::{receipt, Account, env}; -use crate::{ - env, report::{ContractReport, FunctionReport}, CacheOpt -}; +use crate::{ report::{ContractReport, FunctionReport}, CacheOpt}; sol!( #[sol(rpc)] diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index f391b807e7..1e7187552f 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -5,11 +5,11 @@ use alloy::{ providers::ProviderBuilder, sol, sol_types::{ SolCall, SolConstructor} }; -use e2e::{receipt, Account}; +use e2e::{receipt, Account, env}; use pyth_stylus::pyth::mock::create_price_feed_update_data_list; use crate::{ - env, report::{ContractReport, FunctionReport}, CacheOpt + report::{ContractReport, FunctionReport}, CacheOpt }; sol!( diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml index a5a32566f4..62159abfa7 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/Cargo.toml @@ -8,11 +8,11 @@ version.workspace = true [dependencies] pyth-stylus.workspace = true -alloy-primitives.workspace = true +alloy-primitives = { workspace = true, features = ["tiny-keccak"] } alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true - +keccak-const.workspace = true [dev-dependencies] alloy.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index c9d874fbde..fa65541689 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -1,35 +1,76 @@ #![cfg(feature = "e2e")] +use std::println; + use abi::FunctionCalls; use alloy::{ - primitives::{uint, Address, U256}, + primitives::{uint, U256}, sol, }; +use alloy_primitives::{address, Address, FixedBytes}; use e2e::{ receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, - Revert, + Revert,env }; -use eyre::Result; -// use crate::FunctionCallsExample::constructorCall; +use eyre::Result; +use crate::FunctionCallsExample::constructorCall; mod abi; sol!("src/constructor.sol"); -// // ============================================================================ -// // Integration Tests: Function Calls -// // ============================================================================ - -// #[e2e::test] -// async fn constructs(alice: Account) -> Result<()> { -// let contract_addr = alice -// .as_deployer() -// .with_default_constructor::() -// .deploy() -// .await? -// .address()?; -// // // assert_eq!(true, true); - -// Ok(()) -// } +fn str_to_address(input: &str) -> Address { + let mut address_bytes = [0u8; 20]; // Initialize a 20-byte array with zeros + + // Convert the input string to bytes and copy into the 20-byte array + let input_bytes = input.as_bytes(); + let len = input_bytes.len().min(20); // Take up to 20 bytes if input is longer + + address_bytes[..len].copy_from_slice(&input_bytes[..len]); + + Address::new(address_bytes) // Create an Address from the byte array +} + +impl Default for constructorCall { + fn default() -> Self { + ctr("ETH") + } +} + + +fn ctr(key_id: &str) -> constructorCall { + let pyth_addr = env("MOCK_PYTH_ADDRESS").unwrap(); + let address_addr = str_to_address(&pyth_addr); + println!("MOCK_PYTH_ADDRESS: {:?}", pyth_addr); + let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); + let price_id = FixedBytes::<32>::from_slice(&id); + constructorCall { + _pythAddress: address_addr, + _priceId: price_id + } +} + +#[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + + // Instantiate the contract instance + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + + // Perform basic checks to ensure the contract is correctly initialized + // Calling a sample function to validate deployment (e.g., `getPriceUnsafe` as a placeholder) + let price_result = contract.getPriceUnsafe().call().await; + + // Verify that the contract was deployed successfully and the function call does not revert + price_result.expect("The contract deployment or function call failed"); + //assert!(price_result.is_ok(), "The contract deployment or function call failed"); + + Ok(()) +} diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs index 34ec44e34a..b187cc724c 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs @@ -3,62 +3,14 @@ use alloy::sol; sol!( #[sol(rpc)] - contract Pyth { - function approve(address to, uint256 tokenId) external; - #[derive(Debug)] - function balanceOf(address owner) external view returns (uint256 balance); - #[derive(Debug)] - function getApproved(uint256 tokenId) external view returns (address approved); - #[derive(Debug)] - function isApprovedForAll(address owner, address operator) external view returns (bool approved); - #[derive(Debug)] - function ownerOf(uint256 tokenId) external view returns (address ownerOf); - function safeTransferFrom(address from, address to, uint256 tokenId) external; - function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; - function setApprovalForAll(address operator, bool approved) external; - function totalSupply() external view returns (uint256 totalSupply); - function transferFrom(address from, address to, uint256 tokenId) external; - - function mint(address to, uint256 tokenId) external; - function burn(uint256 tokenId) external; - - function paused() external view returns (bool paused); - function pause() external; - function unpause() external; - #[derive(Debug)] - function whenPaused() external view; - #[derive(Debug)] - function whenNotPaused() external view; - - #[derive(Debug)] - function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); - #[derive(Debug)] - function tokenByIndex(uint256 index) external view returns (uint256 tokenId); - - function supportsInterface(bytes4 interface_id) external view returns (bool supportsInterface); - - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); - error ERC721InsufficientApproval(address operator, uint256 tokenId); - error ERC721InvalidApprover(address approver); - error ERC721InvalidOperator(address operator); - error ERC721InvalidOwner(address owner); - error ERC721InvalidReceiver(address receiver); - error ERC721InvalidSender(address sender); - error ERC721NonexistentToken(uint256 tokenId); - error ERC721OutOfBoundsIndex(address owner, uint256 index); - error ERC721EnumerableForbiddenBatchMint(); - error EnforcedPause(); - error ExpectedPause(); - - #[derive(Debug, PartialEq)] - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - #[derive(Debug, PartialEq)] - event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - #[derive(Debug, PartialEq)] - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - #[derive(Debug, PartialEq)] - event Paused(address account); - #[derive(Debug, PartialEq)] - event Unpaused(address account); + contract ProxyCalls { + function getPriceUnsafe(bytes32 id) external; + function getEmaPriceUnsafe(bytes32 id) external; + function getPriceNoOlderThan(bytes32 id, uint age) external; + function getEmaPriceNoOlderThan(bytes32 id, uint age) external; + function getUpdateFee(bytes[] calldata updateData) external returns (uint256); + function getValidTimePeriod() external; + function updatePriceFeeds(bytes[] calldata updateData) external payable; + function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; } ); \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs index 539f71aa7d..5a89cc9aac 100644 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs @@ -14,6 +14,12 @@ pub use error::{Panic, PanicCode, Revert}; pub use event::EventExt; pub use receipt::ReceiptExt; pub use system::{fund_account, provider, Provider, Wallet}; +use eyre::WrapErr; + +/// Load the `name` environment variable. +pub fn env(name: &str) -> eyre::Result { + std::env::var(name).wrap_err(format!("failed to load {name}")) +} /// This macro provides a shorthand for broadcasting the transaction to the /// network. @@ -90,3 +96,4 @@ macro_rules! receipt { $crate::send!($e)?.get_receipt().await }; } + From 3538303dd822351ff65755514b06bd775a0e6b38 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Tue, 5 Nov 2024 21:06:03 +0000 Subject: [PATCH 075/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 4d46dffe27..305d82e221 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -5,32 +5,34 @@ to communicate with the Pyth contract. It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming Pyth data. -## Installation - -###Truffle/Hardhat -If you are using Truffle or Hardhat, simply install the NPM package: +## Features -```bash -npm install @pythnetwork/pyth-sdk-solidity -``` +- Pyth smart contracts, that use external calls [`pyth-solidty -contracts`] library. +- First-class `no_std` support. +- Solidity constructors powered by [`koba`]. +- [Unit] and [integration] test affordances are used in our tests. + -###Foundry +## Installation -If you are using Foundry, you will need to create an NPM project if you don't already have one. -From the root directory of your project, run: +You can import Stylus Contracts from crates.io by adding the following +line to your `Cargo.toml` (We recommend pinning to a specific version): -```bash -npm init -y -npm install @pythnetwork/pyth-sdk-solidity +```toml +[dependencies] +openzeppelin-stylus = "0.1.1" ``` -Then add the following line to your `remappings.txt` file: +Optionally, you can specify a git dependency if you want to have the latest +changes from the `main` branch: -```text -@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity +```toml +[dependencies] +openzeppelin-stylus = { git = "https://github.com/OpenZeppelin/rust-contracts-stylus" } ``` + ## Example Usage To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this From bd054a3b5c7db2c1ab076f4ab142b237a625965e Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Tue, 5 Nov 2024 21:11:18 +0000 Subject: [PATCH 076/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 71 +++++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 305d82e221..ab202dbcc0 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -41,34 +41,49 @@ interface in order to use the prices safely. For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you're interested in. The price feeds available on each chain are listed [below](#target-chains). -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; -import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; - -contract ExampleContract { - IPyth pyth; - - constructor(address pythContract) { - pyth = IPyth(pythContract); - } - - function getBtcUsdPrice( - bytes[] calldata priceUpdateData - ) public payable returns (PythStructs.Price memory) { - // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data - // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package. - // See section "How Pyth Works on EVM Chains" below for more information. - uint fee = pyth.getUpdateFee(priceUpdateData); - pyth.updatePriceFeeds{ value: fee }(priceUpdateData); - - bytes32 priceID = 0xf9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b; - // Read the current value of priceID, aborting the transaction if the price has not been updated in the last 10 - // seconds. - return pyth.getPriceNoOlderThan(priceID, 10); - } +```rust + +use pyth_stylus::pyth::{functions::{ get_price_unsafe}}; + +sol_storage! { + #[entrypoint] + struct FunctionCallsExample { + address pyth_address; + bytes32 price_id; + StoragePrice price; + StoragePriceFeed price_feed; + StoragePrice ema_price; + StoragePriceFeed ema_price_feed; + } +} + +sol! { + + error ArraySizeNotMatch(); + + error CallFailed(); + +} + +#[derive(SolidityError)] +pub enum MultiCallErrors { + + ArraySizeNotMatch(ArraySizeNotMatch), + + CallFailed(CallFailed), + +} + + +impl FunctionCallsExample { + pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { + let price = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; + self.price.set(price); + if price.price > 0 { + return Ok(()); + } + Err(MultiCallErrors::CallFailed(CallFailed{}).into()) + } } ``` From e0f68e49f8d71486142d2ad2561c3b6231dcdcb5 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Tue, 5 Nov 2024 21:13:43 +0000 Subject: [PATCH 077/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index ab202dbcc0..2c1dabacb1 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -8,7 +8,7 @@ It is **strongly recommended** to follow the [consumer best practices](https://d ## Features -- Pyth smart contracts, that use external calls [`pyth-solidty -contracts`] library. +- Pyth smart contracts use external calls [`pyth-solidty-contracts`] library. - First-class `no_std` support. - Solidity constructors powered by [`koba`]. - [Unit] and [integration] test affordances are used in our tests. @@ -21,7 +21,7 @@ line to your `Cargo.toml` (We recommend pinning to a specific version): ```toml [dependencies] -openzeppelin-stylus = "0.1.1" +pyth-stylus = "0.0.1" ``` Optionally, you can specify a git dependency if you want to have the latest @@ -29,14 +29,13 @@ changes from the `main` branch: ```toml [dependencies] -openzeppelin-stylus = { git = "https://github.com/OpenZeppelin/rust-contracts-stylus" } +pyth-stylus = { git = "URL" } ``` - ## Example Usage To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this -interface in order to use the prices safely. +interface to use the prices safely. For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you're interested in. The price feeds available on each chain are listed [below](#target-chains). @@ -76,13 +75,9 @@ pub enum MultiCallErrors { impl FunctionCallsExample { - pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { - let price = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; - self.price.set(price); - if price.price > 0 { - return Ok(()); - } - Err(MultiCallErrors::CallFailed(CallFailed{}).into()) + pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { + let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; + Ok(()) } } From 58f5a2d44c4a8c48d83175d500bb1b7a174648fd Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 6 Nov 2024 17:26:48 +0000 Subject: [PATCH 078/160] chore: worked on bench and e2e test --- .../sdk/stylus/benches/src/function_calls.rs | 2 +- .../stylus/examples/function-calls/src/lib.rs | 11 +++----- .../examples/function-calls/tests/abi/mod.rs | 2 +- .../function-calls/tests/function-calls.rs | 28 +++---------------- .../ethereum/sdk/stylus/scripts/e2e-tests.sh | 13 +++++---- 5 files changed, 18 insertions(+), 38 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index fcc1d56a8f..9c59cfe38c 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -14,7 +14,7 @@ use crate::{ report::{ContractReport, FunctionReport}, CacheOpt}; sol!( #[sol(rpc)] contract FunctionCall{ - function getPriceUnsafe() external ; + function getPriceUnsafe() external returns (int64 price); function getEmaPriceUnsafe() external ; function getPriceNoOlderThan() external ; function getEmaPriceNoOlderThan() external; diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index f5a0afc5d0..1584296ed4 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -49,13 +49,10 @@ pub enum MultiCallErrors { #[public] impl FunctionCallsExample { - pub fn get_price_unsafe(&mut self) -> Result<(), Vec> { - let price = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; - self.price.set(price); - if price.price > 0 { - return Ok(()); - } - Err(MultiCallErrors::CallFailed(CallFailed{}).into()) + pub fn get_price_unsafe(&mut self) -> Result> { + let price_result = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; + self.price.set(price_result); + Ok(price_result.price) } pub fn get_ema_price_unsafe(&mut self) -> Result<(), Vec> { diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs index fc8202dcae..6012685049 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs @@ -4,7 +4,7 @@ use alloy::sol; sol!( #[sol(rpc)] contract FunctionCalls { - function getPriceUnsafe() external ; + function getPriceUnsafe() external returns (int64 price); function getEmaPriceUnsafe() external ; function getPriceNoOlderThan() external ; function getEmaPriceNoOlderThan() external; diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index fa65541689..77a239e3c5 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -20,17 +20,6 @@ mod abi; sol!("src/constructor.sol"); -fn str_to_address(input: &str) -> Address { - let mut address_bytes = [0u8; 20]; // Initialize a 20-byte array with zeros - - // Convert the input string to bytes and copy into the 20-byte array - let input_bytes = input.as_bytes(); - let len = input_bytes.len().min(20); // Take up to 20 bytes if input is longer - - address_bytes[..len].copy_from_slice(&input_bytes[..len]); - - Address::new(address_bytes) // Create an Address from the byte array -} impl Default for constructorCall { fn default() -> Self { @@ -41,10 +30,10 @@ impl Default for constructorCall { fn ctr(key_id: &str) -> constructorCall { let pyth_addr = env("MOCK_PYTH_ADDRESS").unwrap(); - let address_addr = str_to_address(&pyth_addr); - println!("MOCK_PYTH_ADDRESS: {:?}", pyth_addr); + let address_addr = Address::parse_checksummed(&pyth_addr, None).unwrap(); let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); let price_id = FixedBytes::<32>::from_slice(&id); + println!("MOCK_PYTH_ADDRESS: {:?} {:?} {:?}", pyth_addr, address_addr, price_id); constructorCall { _pythAddress: address_addr, _priceId: price_id @@ -60,17 +49,8 @@ async fn constructs(alice: Account) -> Result<()> { .deploy() .await? .address()?; - - // Instantiate the contract instance let contract = FunctionCalls::new(contract_addr, &alice.wallet); - - // Perform basic checks to ensure the contract is correctly initialized - // Calling a sample function to validate deployment (e.g., `getPriceUnsafe` as a placeholder) - let price_result = contract.getPriceUnsafe().call().await; - - // Verify that the contract was deployed successfully and the function call does not revert - price_result.expect("The contract deployment or function call failed"); - //assert!(price_result.is_ok(), "The contract deployment or function call failed"); - + let FunctionCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe().call().await?; + println!("Price: {}", price); Ok(()) } diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index f6448f1024..3bfc4e226f 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -11,10 +11,13 @@ cd "nitro-testnode" cd .. cd "pyth-solidity" -deployed_to=$(forge create ./src/MockPyth.sol:MockPythSample \ - --rpc-url $RPC_URL \ - --private-key $PRIVATE_KEY \ - --constructor-args 100 100 | grep -oP '(?<=Deployed to: )0x[a-fA-F0-9]{40}') +deployed_to=$( + forge script ./script/MockPyth.s.sol:MockPythScript \ + --rpc-url "$RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --broadcast \ + | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 +) export MOCK_PYTH_ADDRESS=$deployed_to cd .. @@ -26,4 +29,4 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" \ No newline at end of file +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" -- --nocapture \ No newline at end of file From 0af7c1a6aa16b2f32137c7697cece99a677d25b6 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 6 Nov 2024 23:33:27 +0000 Subject: [PATCH 079/160] removed log --- target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index 3bfc4e226f..ac1bec6374 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -27,6 +27,7 @@ NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort export RPC_URL=http://localhost:8547 +cargo clean # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) -cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" -- --nocapture \ No newline at end of file +cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" \ No newline at end of file From f389baa60150081ba21fbfe4a6130f96f4907636 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 7 Nov 2024 01:17:24 +0000 Subject: [PATCH 080/160] chore: completed e2e test for function call example --- .../stylus/examples/function-calls/src/lib.rs | 24 ++-- .../examples/function-calls/tests/abi/mod.rs | 10 +- .../function-calls/tests/function-calls.rs | 134 +++++++++++++++++- 3 files changed, 145 insertions(+), 23 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 1584296ed4..9f423ceba4 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -55,18 +55,18 @@ impl FunctionCallsExample { Ok(price_result.price) } - pub fn get_ema_price_unsafe(&mut self) -> Result<(), Vec> { - let _ = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; - Ok(()) + pub fn get_ema_price_unsafe(&mut self) -> Result> { + let price_result = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; + Ok(price_result.price) } - pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { - let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; - Ok(()) + pub fn get_price_no_older_than(&mut self) -> Result> { + let price_result = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; + Ok(price_result.price) } - pub fn get_ema_price_no_older_than(&mut self) -> Result<(), Vec> { - let _ = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; - Ok(()) + pub fn get_ema_price_no_older_than(&mut self) -> Result> { + let price_result = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; + Ok(price_result.price) } pub fn get_update_fee(&mut self) -> Result> { @@ -75,9 +75,9 @@ impl FunctionCallsExample { Ok(fee) } - pub fn get_valid_time_period(&mut self) -> Result<(), Vec> { - let _ = get_valid_time_period(self, self.pyth_address.get())?; - Ok(()) + pub fn get_valid_time_period(&mut self) -> Result> { + let time = get_valid_time_period(self, self.pyth_address.get())?; + Ok(time) } #[payable] diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs index 6012685049..e98ebffd9d 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs @@ -5,11 +5,11 @@ sol!( #[sol(rpc)] contract FunctionCalls { function getPriceUnsafe() external returns (int64 price); - function getEmaPriceUnsafe() external ; - function getPriceNoOlderThan() external ; - function getEmaPriceNoOlderThan() external; - function getUpdateFee() external; - function getValidTimePeriod() external; + function getEmaPriceUnsafe() external returns (int64 price); + function getPriceNoOlderThan() external returns (int64 price); + function getEmaPriceNoOlderThan() external returns (int64 price); + function getUpdateFee() external returns (uint256 fee); + function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds() external payable; function updatePriceFeedsIfNecessary() external payable; } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index 77a239e3c5..cb15fe9d41 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -1,6 +1,6 @@ #![cfg(feature = "e2e")] -use std::println; +use std::{assert_eq, println}; use abi::FunctionCalls; use alloy::{ @@ -23,17 +23,20 @@ sol!("src/constructor.sol"); impl Default for constructorCall { fn default() -> Self { - ctr("ETH") + ctr("ETH", false) } } -fn ctr(key_id: &str) -> constructorCall { - let pyth_addr = env("MOCK_PYTH_ADDRESS").unwrap(); +fn ctr(key_id: &str, invaid_pyth_address: bool) -> constructorCall { + let pyth_addr = if invaid_pyth_address { + "0xdC79c650B6560cBF15391C3F90A833a349735676".to_string() + } else { + env("MOCK_PYTH_ADDRESS").unwrap() + }; let address_addr = Address::parse_checksummed(&pyth_addr, None).unwrap(); let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); let price_id = FixedBytes::<32>::from_slice(&id); - println!("MOCK_PYTH_ADDRESS: {:?} {:?} {:?}", pyth_addr, address_addr, price_id); constructorCall { _pythAddress: address_addr, _priceId: price_id @@ -51,6 +54,125 @@ async fn constructs(alice: Account) -> Result<()> { .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); let FunctionCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe().call().await?; - println!("Price: {}", price); + assert!(price > 0); + Ok(()) +} + +#[e2e::test] +async fn error_provided_invaild_id(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(ctr("BALLON",false)) + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let result = contract.getPriceUnsafe().call().await; + assert!(result.is_err()); + Ok(()) +} + +#[e2e::test] +async fn error_provided_invaild_pyth_address(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_constructor(ctr("BALLON", true)) + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let result = contract.getPriceUnsafe().call().await; + assert!(result.is_err()); + Ok(()) +} + +#[e2e::test] +async fn can_get_price_unsafe(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe().call().await?; + assert!(price > 0); + Ok(()) +} + +#[e2e::test] +async fn can_get_ema_price_unsafe(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getEmaPriceUnsafeReturn { price } = contract.getEmaPriceUnsafe().call().await?; + assert!(price > 0); + Ok(()) +} + + +#[e2e::test] +async fn can_get_price_no_older_than(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getPriceNoOlderThanReturn { price } = contract.getPriceNoOlderThan().call().await?; + assert!(price > 0); + Ok(()) +} + +#[e2e::test] +async fn can_get_ema_price_no_older_than(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getEmaPriceNoOlderThanReturn { price } = contract.getEmaPriceNoOlderThan().call().await?; + assert!(price > 0); + Ok(()) +} + +#[e2e::test] +async fn can_get_fee(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getUpdateFeeReturn { fee } = contract.getUpdateFee().call().await?; + assert_eq!(fee, U256::from(300)); + Ok(()) +} + +#[e2e::test] +async fn can_get_period(alice: Account) -> Result<()> { + // Deploy contract using `alice` account + let contract_addr = alice + .as_deployer() + .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .deploy() + .await? + .address()?; + let contract = FunctionCalls::new(contract_addr, &alice.wallet); + let FunctionCalls::getValidTimePeriodReturn { period } = contract.getValidTimePeriod().call().await?; + assert_eq!(period, U256::from(100000)); Ok(()) } From 9471852689fa0d992e5f8532b331523f25c68922 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 7 Nov 2024 04:23:54 +0000 Subject: [PATCH 081/160] chore : changed pyth script --- .../pyth-solidity/script/MockPyth.s.sol | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index 5548343f6c..6d2ed20eeb 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -32,18 +32,53 @@ contract MockPythScript is Script { function _generateInitialData() internal { for (uint i = 0; i < tickers.length; i++) { priceIds[i] = keccak256(abi.encodePacked(tickers[i])); - uint randomNumber = randNumber(priceIds[i], 10); - int64 price = int64(uint64(randomNumber + randNumber(priceIds[i], 2)) + 1); - uint64 conf = uint64(randomNumber + randNumber(priceIds[i], 3) + 1); - int32 expo = int32(uint32(randomNumber + randNumber(priceIds[i], 40)) + 1); - int64 emaPrice = int64(uint64(randomNumber + randNumber(priceIds[i], 5)) + 1); - uint64 emaConf = uint64(randomNumber + randNumber(priceIds[i], 6) +1); - priceData[i] = pyth_contract.createPriceFeedUpdateData(priceIds[i], price, conf, expo, emaPrice, emaConf, uint64(block.timestamp), 0); + + uint randomBase = uint(keccak256(abi.encodePacked(block.timestamp, priceIds[i], msg.sender))); + + int64 price = int64(uint64(_deriveRandom(randomBase, 1) % 10 )); + uint64 conf = uint64(_deriveRandom(randomBase, 2) % 10); + int32 expo = int32(uint32(_deriveRandom(randomBase, 3) % 40)); + int64 emaPrice = int64(uint64(_deriveRandom(randomBase, 4) % 10)); + uint64 emaConf = uint64(_deriveRandom(randomBase, 5) % 10); + + priceData[i] = pyth_contract.createPriceFeedUpdateData( + priceIds[i], + price, + conf, + expo, + emaPrice, + emaConf, + uint64(block.timestamp), + 0 + ); } + + } + + // Internal function to generate deterministic random numbers from a base seed + function _deriveRandom(uint base, uint salt) internal pure returns (uint) { + return uint(keccak256(abi.encodePacked(base, salt)) ) + 1; } - function randNumber(bytes32 ticker, uint salt) internal returns (uint) { - randNonce++; - return uint(keccak256(abi.encodePacked(block.timestamp, ticker, msg.sender, randNonce, salt))) % randNonce; - } + function uint2str(uint256 _i) internal pure returns (string memory str) { + if (_i == 0) { + return "0"; + } + uint256 j = _i; + uint256 length; + while (j != 0) + { + length++; + j /= 10; + } + bytes memory bstr = new bytes(length); + uint256 k = length; + j = _i; + while (j != 0) + { + bstr[--k] = bytes1(uint8(48 + j % 10)); + j /= 10; + } + str = string(bstr); + } } From dee284693274a09862f22e2eff9047a6ef99c8df Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 7 Nov 2024 07:56:01 +0000 Subject: [PATCH 082/160] chore:completed e2e test --- target_chains/ethereum/sdk/stylus/Cargo.lock | 1 + .../function-calls/tests/function-calls.rs | 16 +- .../stylus/examples/proxy-calls/Cargo.toml | 3 +- .../stylus/examples/proxy-calls/src/lib.rs | 1 - .../examples/proxy-calls/tests/abi/mod.rs | 12 +- .../examples/proxy-calls/tests/proxy-calls.rs | 213 +++++++++++++++++- .../ethereum/sdk/stylus/scripts/e2e-tests.sh | 1 - 7 files changed, 219 insertions(+), 28 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index e52fabf510..fbcea20ef0 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2693,6 +2693,7 @@ dependencies = [ "alloy-sol-types", "e2e", "eyre", + "keccak-const", "mini-alloc", "pyth-stylus", "stylus-sdk", diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index cb15fe9d41..930504b5a8 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -1,16 +1,14 @@ #![cfg(feature = "e2e")] -use std::{assert_eq, println}; +use std::assert_eq; use abi::FunctionCalls; use alloy::{ - primitives::{uint, U256}, + primitives:: U256, sol, }; -use alloy_primitives::{address, Address, FixedBytes}; -use e2e::{ - receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, - Revert,env +use alloy_primitives::{ Address, FixedBytes}; +use e2e::{ Account, ReceiptExt,env }; use eyre::Result; @@ -48,13 +46,11 @@ async fn constructs(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() .deploy() .await? .address()?; - let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe().call().await?; - assert!(price > 0); + let _ = FunctionCalls::new(contract_addr, &alice.wallet); Ok(()) } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml index 0429f8a5e5..791cb2bb07 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/Cargo.toml @@ -8,10 +8,11 @@ version.workspace = true [dependencies] pyth-stylus.workspace = true -alloy-primitives.workspace = true +alloy-primitives = { workspace = true, features = ["tiny-keccak"] } alloy-sol-types.workspace = true stylus-sdk.workspace = true mini-alloc.workspace = true +keccak-const.workspace = true [dev-dependencies] alloy.workspace = true diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs index 5e211716ca..90f1c1624e 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs @@ -1,7 +1,6 @@ #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; - use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; use pyth_stylus::pyth::pyth_contract::PythContract; diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs index b187cc724c..fae79db057 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs @@ -4,12 +4,12 @@ use alloy::sol; sol!( #[sol(rpc)] contract ProxyCalls { - function getPriceUnsafe(bytes32 id) external; - function getEmaPriceUnsafe(bytes32 id) external; - function getPriceNoOlderThan(bytes32 id, uint age) external; - function getEmaPriceNoOlderThan(bytes32 id, uint age) external; - function getUpdateFee(bytes[] calldata updateData) external returns (uint256); - function getValidTimePeriod() external; + function getPriceUnsafe(bytes32 id) external returns (uint8[] price); + function getEmaPriceUnsafe(bytes32 id) external returns (uint8[] price); + function getPriceNoOlderThan(bytes32 id, uint age) external returns (uint8[] price); + function getEmaPriceNoOlderThan(bytes32 id, uint age) external returns (uint8[] price); + function getUpdateFee(bytes[] calldata updateData) external returns (uint256 fee); + function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds(bytes[] calldata updateData) external payable; function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs index 57bba732dc..5bb4b22e5b 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs @@ -1,20 +1,215 @@ #![cfg(feature = "e2e")] -use alloy::{hex, sol}; -use e2e::{receipt,ReceiptExt,Account}; +use std::assert_eq; + +use abi::ProxyCalls; +use alloy::{ + primitives:: U256, + sol, +}; +use alloy_primitives::{Address, FixedBytes}; +use e2e::{ + Account, ReceiptExt,env +}; +use alloy_sol_types::SolValue; + use eyre::Result; +use pyth_stylus::pyth::{mock::create_price_feed_update_data_list, types::Price}; +use crate::ProxyCallsExample::constructorCall; mod abi; + sol!("src/constructor.sol"); // // ============================================================================ // // Integration Tests: Proxy Calls // // ============================================================================ -// #[e2e::test] -// async fn constructs(alice: Account) -> Result<()> { -// // // let contract_addr = alice.as_deployer().deploy().await?.address()?; -// // // // assert_eq!(true, true); - -// Ok(()) -// } +impl Default for constructorCall { + fn default() -> Self { + ctr(false) + } +} + +fn generate_pyth_id_from_str(key_id: &str) -> FixedBytes<32> { + let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); + let price_id = FixedBytes::<32>::from_slice(&id); + price_id +} + +fn ctr(invaid_pyth_address: bool) -> constructorCall { + let pyth_addr = if invaid_pyth_address { + "0xdC79c650B6560cBF15391C3F90A833a349735676".to_string() + } else { + env("MOCK_PYTH_ADDRESS").unwrap() + }; + let address_addr = Address::parse_checksummed(&pyth_addr, None).unwrap(); + constructorCall { + _pythAddress: address_addr + } +} + +#[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("ETH"); + contract.getPriceUnsafe(id).call().await?; + Ok(()) + } + + +#[e2e::test] +async fn can_get_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("ETH"); + let ProxyCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe(id).call().await?; + let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + assert!(decoded_price.price > 0_i64); + assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.expo > 0_i32); + assert!(decoded_price.publish_time > U256::from(0)); + Ok(()) + } + +#[e2e::test] +async fn error_provided_invaild_id_get_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("BALLON"); + let price_result = contract.getPriceUnsafe(id).call().await; + assert!(price_result.is_err()); + Ok(()) + } + + +#[e2e::test] +async fn can_get_ema_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("ETH"); + let ProxyCalls::getEmaPriceUnsafeReturn { price } = contract.getEmaPriceUnsafe(id).call().await?; + let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + assert!(decoded_price.price > 0_i64); + assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.expo > 0_i32); + assert!(decoded_price.publish_time > U256::from(0)); + Ok(()) + } + + + #[e2e::test] +async fn error_provided_invaild_id_get_ema_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("BALLON"); + let price_result = contract.getEmaPriceUnsafe(id).call().await; + assert!(price_result.is_err()); + Ok(()) + } + +#[e2e::test] +async fn can_get_price_no_older_than(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("ETH"); + let ProxyCalls::getPriceNoOlderThanReturn { price } = contract.getPriceNoOlderThan(id, U256::from(1000)).call().await?; + let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + assert!(decoded_price.price > 0_i64); + assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.expo > 0_i32); + assert!(decoded_price.publish_time > U256::from(0)); + Ok(()) + } + +#[e2e::test] +async fn error_provided_invaild_id_get_price_no_older_than(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("BALLON"); + let price_result = contract.getPriceNoOlderThan(id, U256::from(1000)).call().await; + assert!(price_result.is_err()); + Ok(()) + } + + #[e2e::test] +async fn error_provided_invaild_period_get_price_no_older_than(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let id = generate_pyth_id_from_str("SOL"); + let price_result = contract.getPriceNoOlderThan(id, U256::from(1)).call().await; + assert!(price_result.is_err()); + Ok(()) + } + + #[e2e::test] +async fn can_get_fee(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let (data , _id) = create_price_feed_update_data_list(); + let ProxyCalls::getUpdateFeeReturn { fee } = contract.getUpdateFee(data).call().await?; + assert_eq!(fee , U256::from(300)); + Ok(()) + } + + #[e2e::test] + async fn can_get_valid_time_peroid(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = ProxyCalls::new(contract_addr, &alice.wallet); + let ProxyCalls::getValidTimePeriodReturn { period } = contract.getValidTimePeriod().call().await?; + assert_eq!(period , U256::from(100000)); + Ok(()) + } + \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index ac1bec6374..8dc7fa36e5 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -27,7 +27,6 @@ NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort export RPC_URL=http://localhost:8547 -cargo clean # We should use stable here once nitro-testnode is updated and the contracts fit # the size limit. Work tracked [here](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/87) cargo +"$NIGHTLY_TOOLCHAIN" test --features std,e2e --test "*" \ No newline at end of file From 9a8d23d7bcc3915dd9ad4ad12e0d3952db0d5191 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 7 Nov 2024 07:56:18 +0000 Subject: [PATCH 083/160] chore:benches --- .../sdk/stylus/benches/src/function_calls.rs | 14 +++++++------- .../ethereum/sdk/stylus/benches/src/proxy_calls.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 9c59cfe38c..2ae5047668 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -14,14 +14,14 @@ use crate::{ report::{ContractReport, FunctionReport}, CacheOpt}; sol!( #[sol(rpc)] contract FunctionCall{ - function getPriceUnsafe() external returns (int64 price); - function getEmaPriceUnsafe() external ; - function getPriceNoOlderThan() external ; - function getEmaPriceNoOlderThan() external; - function getUpdateFee() external; - function getValidTimePeriod() external; + function getPriceUnsafe() external returns (int64 price); + function getEmaPriceUnsafe() external returns (int64 price); + function getPriceNoOlderThan() external returns (int64 price); + function getEmaPriceNoOlderThan() external returns (int64 price); + function getUpdateFee() external returns (uint256 fee); + function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds() external payable; - function updatePriceFeedsIfNecessary() external payable; + function updatePriceFeedsIfNecessary() external payable; } ); diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 1e7187552f..abed9a598a 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -60,7 +60,7 @@ pub async fn run_with( let time_frame = uint!(10000_U256); let age = uint!(10000_U256); - let (data, ids) = create_price_feed_update_data_list(); + let (data, _ids) = create_price_feed_update_data_list(); let _ = receipt!(contract.getPriceUnsafe(id))?; From ebb4858ec5fb12bca278820de725c5dbf5d51cca Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:01:00 +0000 Subject: [PATCH 084/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 29 ++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 2c1dabacb1..889eb4e75b 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -34,8 +34,8 @@ pyth-stylus = { git = "URL" } ## Example Usage -To consume prices you should use the [`IPyth`](IPyth.sol) interface. Please make sure to read the documentation of this -interface to use the prices safely. +To consume prices you should use the functions interface. Please make sure to read the documentation of this +functions to use the prices safely. For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you're interested in. The price feeds available on each chain are listed [below](#target-chains). @@ -83,19 +83,30 @@ impl FunctionCallsExample { ``` -## How Pyth Works on EVM Chains +Another approach is not to use the call functions but use the Pyth contract, that implment the IPyth functions -Pyth prices are published on Pythnet, and relayed to EVM chains using the [Wormhole Network](https://wormholenetwork.com/) as a cross-chain message passing bridge. The Wormhole Network observes when Pyth prices on Pythnet have changed and publishes an off-chain signed message attesting to this fact. This is explained in more detail [here](https://docs.wormholenetwork.com/wormhole/). +```rust +#![cfg_attr(not(test), no_std, no_main)] +extern crate alloc; -This signed message can then be submitted to the Pyth contract on the EVM networks along the required update fee for it, which will verify the Wormhole message and update the Pyth contract with the new price. +use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; +use pyth_stylus::pyth::pyth_contract::PythContract; -Please refer to [Pyth On-Demand Updates page](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand) for more information. -## Solidity Target Chains +sol_storage! { + #[entrypoint] + struct ProxyCallsExample { + #[borrow] + PythContract pyth + } +} -[This](https://docs.pyth.network/documentation/pythnet-price-feeds/evm#networks) document contains list of the EVM networks that Pyth is available on. +#[public] +#[inherit(PythContract)] +impl ProxyCallsExample { +} -You can find a list of available price feeds [here](https://pyth.network/developers/price-feed-ids/). +``` ## Mocking Pyth From 0f6da8609f1cb5506b05323df97e5a562d2de33b Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:05:36 +0000 Subject: [PATCH 085/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 889eb4e75b..e694c1e532 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -38,7 +38,7 @@ To consume prices you should use the functions interface. Please make sure to re functions to use the prices safely. For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed -you're interested in. The price feeds available on each chain are listed [below](#target-chains). +you're interested in ```rust @@ -83,7 +83,7 @@ impl FunctionCallsExample { ``` -Another approach is not to use the call functions but use the Pyth contract, that implment the IPyth functions +Another approach is not to use the call functions but use the Pyth contract, that implement the IPyth functions ```rust #![cfg_attr(not(test), no_std, no_main)] @@ -110,13 +110,7 @@ impl ProxyCallsExample { ## Mocking Pyth -[MockPyth](./MockPyth.sol) is a mock contract that you can use and deploy locally to mock Pyth contract behaviour. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds (the struct defined in [PythStructs](./PythStructs.sol)) as its argument. You can create encoded price feeds either by using web3.js or ethers ABI utilities or calling `createPriceFeedUpdateData` function in the mock contract. - -## Development - -### ABIs - -When making changes to a contract interface, please make sure to update the ABI files too. You can update it using `npm run generate-abi` and it will update the ABI files in [abis](./abis) directory. If you create a new contract, you also need to add the contract name in [the ABI generation script](./scripts/generateAbi.js#L5) so the script can create the ABI file for the new contract as well. +[MockPyth](./mock.rs) is a mock contract you can use and deploy locally to mock Pyth contract behavior. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds as its argument. You can create encoded price feeds either by calling `create_price_feed_update_data` function in the mock contract, That functions also exist in the function file. ### Releases From 4b84ac0ed8aaab06598fb3ca5d3abf3d06dfbcf5 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:10:44 +0000 Subject: [PATCH 086/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index e694c1e532..b044b3ae95 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -110,7 +110,7 @@ impl ProxyCallsExample { ## Mocking Pyth -[MockPyth](./mock.rs) is a mock contract you can use and deploy locally to mock Pyth contract behavior. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds as its argument. You can create encoded price feeds either by calling `create_price_feed_update_data` function in the mock contract, That functions also exist in the function file. +[MockPyth](./mock.rs) is a mock contract you can use and deploy locally to mock Pyth contract behavior. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds as its argument. You can create encoded price feeds by calling `create_price_feed_update_data` function in the mock contract, That functions also exist in the function file. ### Releases From d5b33e0b0de932b44a6a9b2630e614da9216d902 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:12:05 +0000 Subject: [PATCH 087/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 60 ++++++++------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index b044b3ae95..4042cf1d3b 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -1,31 +1,30 @@ -# Pyth Stylus SDK +Here's a refined version of the documentation for clarity, consistency, and accuracy: + +--- -This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle using Solidity. Also, it contains [the Pyth Interface ABI](./abis/IPyth.json) that you can use in your libraries -to communicate with the Pyth contract. +# Pyth Stylus SDK -It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming Pyth data. +This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle in Solidity. It also includes the [Pyth Interface ABI](./abis/IPyth.json), which can be used in your libraries to interact with the Pyth contract. +It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming data from Pyth. ## Features -- Pyth smart contracts use external calls [`pyth-solidty-contracts`] library. -- First-class `no_std` support. -- Solidity constructors powered by [`koba`]. -- [Unit] and [integration] test affordances are used in our tests. - +- Integrates with the [`pyth-solidity-contracts`] library for external calls to Pyth smart contracts. +- Provides first-class `no_std` support. +- Includes Solidity constructors powered by [`koba`]. +- Supports both [unit] and [integration] test affordances for thorough testing. ## Installation -You can import Stylus Contracts from crates.io by adding the following -line to your `Cargo.toml` (We recommend pinning to a specific version): +To add the Stylus Contracts from crates.io, add the following line to your `Cargo.toml` (pinning to a specific version is recommended): ```toml [dependencies] pyth-stylus = "0.0.1" ``` -Optionally, you can specify a git dependency if you want to have the latest -changes from the `main` branch: +For the latest changes from the `main` branch, you can also specify a git dependency: ```toml [dependencies] @@ -34,15 +33,12 @@ pyth-stylus = { git = "URL" } ## Example Usage -To consume prices you should use the functions interface. Please make sure to read the documentation of this -functions to use the prices safely. +To consume prices, use the functions interface. Be sure to read the function documentation to ensure safe use of price data. -For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed -you're interested in +For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you are interested in: ```rust - -use pyth_stylus::pyth::{functions::{ get_price_unsafe}}; +use pyth_stylus::pyth::functions::get_price_unsafe; sol_storage! { #[entrypoint] @@ -57,42 +53,33 @@ sol_storage! { } sol! { - error ArraySizeNotMatch(); - error CallFailed(); - } #[derive(SolidityError)] pub enum MultiCallErrors { - ArraySizeNotMatch(ArraySizeNotMatch), - CallFailed(CallFailed), - } - impl FunctionCallsExample { pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { - let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; + let _ = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; Ok(()) } } - ``` -Another approach is not to use the call functions but use the Pyth contract, that implement the IPyth functions +Alternatively, you can interact directly with the Pyth contract, which implements the IPyth functions, instead of using call functions: -```rust +```rust #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; -use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; +use stylus_sdk::prelude::{entrypoint, public, sol_storage}; use pyth_stylus::pyth::pyth_contract::PythContract; - sol_storage! { #[entrypoint] struct ProxyCallsExample { @@ -105,16 +92,15 @@ sol_storage! { #[inherit(PythContract)] impl ProxyCallsExample { } - ``` ## Mocking Pyth -[MockPyth](./mock.rs) is a mock contract you can use and deploy locally to mock Pyth contract behavior. To set and update price feeds you should call `updatePriceFeeds` and provide an array of encoded price feeds as its argument. You can create encoded price feeds by calling `create_price_feed_update_data` function in the mock contract, That functions also exist in the function file. +[MockPyth](./mock.rs) is a mock contract that can be deployed locally to simulate Pyth contract behavior. To set and update price feeds, call `updatePriceFeeds` and provide an array of encoded price feeds as the argument. Encoded price feeds can be created using the `create_price_feed_update_data` function in the mock contract, which is also available in the functions module. ### Releases -We use [Semantic Versioning](https://semver.org/) for our releases. In order to release a new version of this package and publish it to npm, follow these steps: +We use [Semantic Versioning](https://semver.org/) for our releases. To release a new version of this package and publish it to npm, follow these steps: -1. Run `npm version --no-git-tag-version`. This command will update the version of the package. Then push your changes to github. -2. Once your change is merged into `main`, create a release with tag `v` like `v1.5.2`, and a github action will automatically publish the new version of this package to npm. +1. Run `npm version --no-git-tag-version` to update the package version, then push your changes to GitHub. +2. Once the change is merged into `main`, create a release with the tag `v`, such as `v1.5.2`. A GitHub action will automatically publish the new version of the package to npm. From 87ea83b81149ccdef3d32757c72eba0b1b057ca1 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:13:30 +0000 Subject: [PATCH 088/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 4042cf1d3b..0a1eca0a97 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -1,7 +1,3 @@ -Here's a refined version of the documentation for clarity, consistency, and accuracy: - ---- - # Pyth Stylus SDK This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle in Solidity. It also includes the [Pyth Interface ABI](./abis/IPyth.json), which can be used in your libraries to interact with the Pyth contract. From de4425346059ca42fbb0e84f7ced5301ed2d0712 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 7 Nov 2024 13:44:06 +0000 Subject: [PATCH 089/160] chore: removed docs --- .../ethereum/sdk/stylus/docs/antora.yml | 5 - .../sdk/stylus/docs/modules/ROOT/nav.adoc | 4 - .../stylus/docs/modules/ROOT/pages/index.adoc | 7 - .../docs/modules/ROOT/pages/utilities.adoc | 43 -- .../sdk/stylus/docs/package-lock.json | 440 ------------------ .../ethereum/sdk/stylus/docs/package.json | 15 - 6 files changed, 514 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/docs/antora.yml delete mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc delete mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc delete mode 100644 target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc delete mode 100644 target_chains/ethereum/sdk/stylus/docs/package-lock.json delete mode 100644 target_chains/ethereum/sdk/stylus/docs/package.json diff --git a/target_chains/ethereum/sdk/stylus/docs/antora.yml b/target_chains/ethereum/sdk/stylus/docs/antora.yml deleted file mode 100644 index 9c51498218..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/antora.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: contracts-stylus -title: Contracts for Stylus -version: 0.1.0 -nav: - - modules/ROOT/nav.adoc diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc deleted file mode 100644 index e1c24eeb7d..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/nav.adoc +++ /dev/null @@ -1,4 +0,0 @@ -* xref:index.adoc[Overview] -* xref:deploy.adoc[Deploying Contracts] - -*** xref:erc20.adoc#erc20-token-extensions[Extensions] diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc deleted file mode 100644 index 4db1e5b3fb..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/index.adoc +++ /dev/null @@ -1,7 +0,0 @@ -:stylus: https://docs.arbitrum.io/stylus/stylus-gentle-introduction[Stylus] - -= Contracts for Stylus - -*A library for secure smart contract development* written in Rust for {stylus}. - -NOTE: You can track our roadmap in our https://github.com/orgs/OpenZeppelin/projects/35[Github Project]. diff --git a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc b/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc deleted file mode 100644 index 0d6bc56906..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/modules/ROOT/pages/utilities.adoc +++ /dev/null @@ -1,43 +0,0 @@ -= Utilities - -The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. -For a complete list, check out the https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/index.html[API Reference]. -Here are some of the more popular ones. - -[[introspection]] -== Introspection - -It's frequently helpful to know whether a contract supports an interface you'd like to use. -https://eips.ethereum.org/EIPS/eip-165[`ERC-165`] is a standard that helps do runtime interface detection. -Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: - -* https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html[`IERC165`] — this is the ERC-165 trait that defines https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`]. In order to implement ERC-165 interface detection, you should manually expose https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface[`supportsInterface`] function in your contract. - -[source,rust] ----- -sol_storage! { - #[entrypoint] - struct Erc721Example { - #[borrow] - Erc721 erc721; - } -} - -#[public] -#[inherit(Erc721)] -impl Erc721Example { - pub fn supports_interface(interface_id: FixedBytes<4>) -> bool { - Erc721::supports_interface(interface_id) - } -} - ----- - -[[structures]] -== Structures - -Some use cases require more powerful data structures than arrays and mappings offered natively in alloy and the Stylus sdk. -Contracts for Stylus provides these libraries for enhanced data structure management: - -- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/bitmap/index.html[`BitMaps`]: Store packed booleans in storage. -- https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/structs/checkpoints/index.html[`Checkpoints`]: Checkpoint values with built-in lookups. diff --git a/target_chains/ethereum/sdk/stylus/docs/package-lock.json b/target_chains/ethereum/sdk/stylus/docs/package-lock.json deleted file mode 100644 index 63f271ded7..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/package-lock.json +++ /dev/null @@ -1,440 +0,0 @@ -{ - "name": "docs", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "docs", - "version": "0.1.0", - "license": "ISC", - "devDependencies": { - "@openzeppelin/docs-utils": "^0.1.2" - } - }, - "node_modules/@frangio/servbot": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", - "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", - "dev": true, - "engines": { - "node": ">=12.x", - "pnpm": "7.5.1" - } - }, - "node_modules/@openzeppelin/docs-utils": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.5.tgz", - "integrity": "sha512-GfqXArKmdq8rv+hsP+g8uS1VEkvMIzWs31dCONffzmqFwJ+MOsaNQNZNXQnLRgUkzk8i5mTNDjJuxDy+aBZImQ==", - "dev": true, - "dependencies": { - "@frangio/servbot": "^0.2.5", - "chalk": "^3.0.0", - "chokidar": "^3.5.3", - "env-paths": "^2.2.0", - "find-up": "^4.1.0", - "is-port-reachable": "^3.0.0", - "js-yaml": "^3.13.1", - "lodash.startcase": "^4.4.0", - "minimist": "^1.2.0" - }, - "bin": { - "oz-docs": "oz-docs.js" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-port-reachable": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", - "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - } - } -} diff --git a/target_chains/ethereum/sdk/stylus/docs/package.json b/target_chains/ethereum/sdk/stylus/docs/package.json deleted file mode 100644 index ac29e6bf72..0000000000 --- a/target_chains/ethereum/sdk/stylus/docs/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "docs", - "version": "0.1.0", - "scripts": { - "docs": "oz-docs -c .", - "docs:watch": "npm run docs watch", - "prepare-docs": "" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@openzeppelin/docs-utils": "^0.1.2" - } -} From 670456afb8c319d21702cec067d68760c0c4a5fd Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:47:02 +0000 Subject: [PATCH 090/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 0a1eca0a97..c6a5903dc6 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -34,7 +34,7 @@ To consume prices, use the functions interface. Be sure to read the function doc For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you are interested in: ```rust -use pyth_stylus::pyth::functions::get_price_unsafe; +use pyth_stylus::pyth::functions::get_price_no_older_than; sol_storage! { #[entrypoint] @@ -48,16 +48,6 @@ sol_storage! { } } -sol! { - error ArraySizeNotMatch(); - error CallFailed(); -} - -#[derive(SolidityError)] -pub enum MultiCallErrors { - ArraySizeNotMatch(ArraySizeNotMatch), - CallFailed(CallFailed), -} impl FunctionCallsExample { pub fn get_price_no_older_than(&mut self) -> Result<(), Vec> { From 4b8f43cd28a416313eff346d4c1e61645f32ef8d Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Thu, 7 Nov 2024 13:49:18 +0000 Subject: [PATCH 091/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index c6a5903dc6..e2b59e7516 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -31,7 +31,7 @@ pyth-stylus = { git = "URL" } To consume prices, use the functions interface. Be sure to read the function documentation to ensure safe use of price data. -For example, to read the latest price, call [`getPriceNoOlderThan`](IPyth.sol) with the Price ID of the price feed you are interested in: +For example, to read the latest price, call [`getPriceNoOlderThan`](https://github.com/Ifechukwudaniel/pyth-crosschain/blob/stylus-sdk/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs) with the Price ID of the price feed you are interested in: ```rust use pyth_stylus::pyth::functions::get_price_no_older_than; From 0625759ff638f3ad0ecdd698ee2967f933703ca0 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:02:13 +0000 Subject: [PATCH 092/160] Update CODEOWNERS --- target_chains/ethereum/sdk/stylus/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS b/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS index da7375eb96..4f5d950206 100644 --- a/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS +++ b/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS @@ -1 +1 @@ -* @bidzyyys @qalisander +* @Ifechukwudaniel From 1924721186a1c825d338677ae4ba3adfd955099e Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:03:47 +0000 Subject: [PATCH 093/160] Delete target_chains/ethereum/sdk/stylus/SECURITY.md --- target_chains/ethereum/sdk/stylus/SECURITY.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/SECURITY.md diff --git a/target_chains/ethereum/sdk/stylus/SECURITY.md b/target_chains/ethereum/sdk/stylus/SECURITY.md deleted file mode 100644 index 3ebfc6fb66..0000000000 --- a/target_chains/ethereum/sdk/stylus/SECURITY.md +++ /dev/null @@ -1,2 +0,0 @@ -# Security - From b7e1a309219ccddea59b466330968ade50fc8a14 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:04:56 +0000 Subject: [PATCH 094/160] Delete target_chains/ethereum/sdk/stylus/lib/crypto directory --- .../ethereum/sdk/stylus/lib/crypto/Cargo.toml | 23 - .../ethereum/sdk/stylus/lib/crypto/README.md | 29 - .../sdk/stylus/lib/crypto/src/hash.rs | 192 ----- .../sdk/stylus/lib/crypto/src/keccak.rs | 49 -- .../ethereum/sdk/stylus/lib/crypto/src/lib.rs | 28 - .../sdk/stylus/lib/crypto/src/merkle.rs | 664 ------------------ 6 files changed, 985 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml deleted file mode 100644 index 777aeacaed..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "openzeppelin-crypto" -description = "Cryptographic Utilities" -edition.workspace = true -categories = ["cryptography", "algorithms", "no-std", "wasm"] -keywords = ["crypto", "web3", "blockchain", "merkle"] -license.workspace = true -repository.workspace = true -version.workspace = true - -[dependencies] -mini-alloc.workspace = true -tiny-keccak.workspace = true - -[dev-dependencies] -hex-literal = "0.4.1" -rand.workspace = true - -[features] -std = [] - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/README.md b/target_chains/ethereum/sdk/stylus/lib/crypto/README.md deleted file mode 100644 index 9b06a9a2ed..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Cryptographic Utilities - -Common cryptographic procedures for a blockchain environment. - -> [!WARNING] -> Note that `crypto` is still `0.*.*`, so breaking changes -> [may occur at any time](https://semver.org/#spec-item-4). If you must depend -> on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. - -## Verifying Merkle Proofs - -[`merkle.rs`](./src/merkle.rs) provides: - -- A `verify` function which can prove that some value is part of a - [Merkle tree]. -- A `verify_multi_proof` function which can prove multiple values are part of a - [Merkle tree]. - -[Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree - -## Feature Flags - -This crate exposes its modules behind feature gates to ensure the bare minimum -is included in consumer codebases. You can check the current feature flags in -the [Cargo.toml](./Cargo.toml) file. - -## Security - -Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs deleted file mode 100644 index b8f98fd5d2..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/hash.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Generic hashing support. -//! -//! This module provides a generic way to compute the [hash] of a value. It is -//! intended to be used as a replacement for [`core::hash`], which we can't use -//! because [`core::hash::Hasher::finish`] returns a `u64`. -//! -//! [hash]: https://en.wikipedia.org/wiki/Hash_function - -/// A hashable type. -/// -/// Types implementing `Hash` are able to be [`Hash::hash`]ed with an instance -/// of [`Hasher`]. -pub trait Hash { - /// Feeds this value into the given [`Hasher`]. - fn hash(&self, state: &mut H); -} - -/// A trait for hashing an arbitrary stream of bytes. -/// -/// Instances of `Hasher` usually represent state that is changed while hashing -/// data. -/// -/// `Hasher` provides a fairly basic interface for retrieving the generated hash -/// (with [`Hasher::finalize`]), and absorbing an arbitrary number of bytes -/// (with [`Hasher::update`]). Most of the time, [`Hasher`] instances are used -/// in conjunction with the [`Hash`] trait. -pub trait Hasher { - /// The output type of this hasher. - /// - /// For [`core::hash`] types, it's `u64`. For [`tiny_keccak`], it's `[u8]`. - /// For this crate, it's `[u8; 32]`. - type Output; - - /// Absorb additional input. Can be called multiple times. - fn update(&mut self, input: impl AsRef<[u8]>); - - /// Output the hashing algorithm state. - fn finalize(self) -> Self::Output; -} - -/// A trait for creating instances of [`Hasher`]. -/// -/// A `BuildHasher` is typically used (e.g., by [`HashMap`]) to create -/// [`Hasher`]s for each key such that they are hashed independently of one -/// another, since [`Hasher`]s contain state. -/// -/// For each instance of `BuildHasher`, the [`Hasher`]s created by -/// [`build_hasher`] should be identical. That is, if the same stream of bytes -/// is fed into each hasher, the same output will also be generated. -/// -/// # Examples -/// -/// ```rust -/// use openzeppelin_crypto::KeccakBuilder; -/// use openzeppelin_crypto::hash::{BuildHasher, Hash, Hasher}; -/// -/// let b = KeccakBuilder; -/// let mut hasher_1 = b.build_hasher(); -/// let mut hasher_2 = b.build_hasher(); -/// -/// hasher_1.update([1]); -/// hasher_2.update([1]); -/// -/// assert_eq!(hasher_1.finalize(), hasher_2.finalize()); -/// ``` -/// -/// [`build_hasher`]: BuildHasher::build_hasher -/// [`HashMap`]: ../../std/collections/struct.HashMap.html -pub trait BuildHasher { - /// Type of the hasher that will be created. - type Hasher: Hasher; - - /// Creates a new hasher. - /// - /// Each call to `build_hasher` on the same instance should produce - /// identical [`Hasher`]s. - /// - /// # Examples - /// - /// ```rust - /// use openzeppelin_crypto::KeccakBuilder; - /// use openzeppelin_crypto::hash::BuildHasher; - /// - /// let b = KeccakBuilder; - /// let hasher = b.build_hasher(); - /// ``` - fn build_hasher(&self) -> Self::Hasher; - - /// Calculates the hash of a single value. - /// - /// This is intended as a convenience for code which *consumes* hashes, such - /// as the implementation of a hash table or in unit tests that check - /// whether a custom [`Hash`] implementation behaves as expected. - /// - /// This must not be used in any code which *creates* hashes, such as in an - /// implementation of [`Hash`]. The way to create a combined hash of - /// multiple values is to call [`Hash::hash`] multiple times using the same - /// [`Hasher`], not to call this method repeatedly and combine the results. - /// - /// # Examples - /// - /// ```rust - /// use openzeppelin_crypto::KeccakBuilder; - /// use openzeppelin_crypto::hash::{BuildHasher, Hash}; - /// - /// let b = KeccakBuilder; - /// let hash_1 = b.hash_one([0u8; 32]); - /// let hash_2 = b.hash_one([0u8; 32]); - /// assert_eq!(hash_1, hash_2); - /// - /// let hash_1 = b.hash_one([1u8; 32]); - /// assert_ne!(hash_1, hash_2); - /// ``` - fn hash_one( - &self, - h: Hashable, - ) -> ::Output - where - Hashable: Hash, - Self: Sized, - Self::Hasher: Hasher, - { - let mut hasher = self.build_hasher(); - h.hash(&mut hasher); - hasher.finalize() - } -} - -/// Hash the pair `(a, b)` with `state`. -#[allow(clippy::module_name_repetitions)] -#[inline] -pub fn hash_pair(a: &H, b: &H, mut state: S) -> S::Output -where - H: Hash + ?Sized, - S: Hasher, -{ - a.hash(&mut state); - b.hash(&mut state); - state.finalize() -} - -/// Sort the pair `(a, b)` and hash the result with `state`. Frequently used -/// when working with merkle proofs. -#[inline] -pub fn commutative_hash_pair(a: &H, b: &H, state: S) -> S::Output -where - H: Hash + PartialOrd, - S: Hasher, -{ - if a > b { - hash_pair(b, a, state) - } else { - hash_pair(a, b, state) - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - use super::{commutative_hash_pair, hash_pair, BuildHasher, Hash, Hasher}; - use crate::KeccakBuilder; - - impl Hash for &[u8] { - fn hash(&self, state: &mut H) { - state.update(self); - } - } - - #[test] - fn hashes_pairs() { - let builder = KeccakBuilder; - let a = [1u8].as_slice(); - let b = [2u8].as_slice(); - - let r1 = hash_pair(&a, &b, builder.build_hasher()); - let r2 = hash_pair(&a, &b, builder.build_hasher()); - assert_eq!(r1, r2); - - let r3 = hash_pair(&b, &a, builder.build_hasher()); - assert_ne!(r1, r3); - } - - #[test] - fn commutatively_hashes_pairs() { - let builder = KeccakBuilder; - let a = [1u8].as_slice(); - let b = [2u8].as_slice(); - - let r1 = commutative_hash_pair(&a, &b, builder.build_hasher()); - let r2 = commutative_hash_pair(&b, &a, builder.build_hasher()); - assert_eq!(r1, r2); - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs deleted file mode 100644 index 136d4afb74..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/keccak.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! An interface to the default hashing algorithm used in this library's [merkle -//! proofs][crate]. -use tiny_keccak::{Hasher as TinyHasher, Keccak}; - -use crate::hash::{BuildHasher, Hash, Hasher}; - -/// The default [`Hasher`] builder used in this library's [merkle -/// proofs][crate]. -/// -/// It instantiates a [`Keccak256`] hasher. -#[allow(clippy::module_name_repetitions)] -pub struct KeccakBuilder; - -impl BuildHasher for KeccakBuilder { - type Hasher = Keccak256; - - #[inline] - fn build_hasher(&self) -> Self::Hasher { - Keccak256(Keccak::v256()) - } -} - -/// The default [`Hasher`] used in this library's [merkle proofs][crate]. -/// -/// The underlying implementation is guaranteed to match that of the -/// `keccak256` algorithm, commonly used in Ethereum. -#[allow(clippy::module_name_repetitions)] -pub struct Keccak256(Keccak); - -impl Hasher for Keccak256 { - type Output = [u8; 32]; - - fn update(&mut self, input: impl AsRef<[u8]>) { - self.0.update(input.as_ref()); - } - - fn finalize(self) -> Self::Output { - let mut buffer = [0u8; 32]; - self.0.finalize(&mut buffer); - buffer - } -} - -impl Hash for [u8; 32] { - #[inline] - fn hash(&self, state: &mut H) { - state.update(self); - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs deleted file mode 100644 index 8c7fc0127b..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -/*! -Common cryptographic procedures for a blockchain environment. - -> Note that `crypto` is still `0.*.*`, so breaking changes -> [may occur at any time](https://semver.org/#spec-item-4). If you must depend -> on `crypto`, we recommend pinning to a specific version, i.e., `=0.y.z`. - -## Verifying Merkle Proofs - -[`merkle.rs`](./src/merkle.rs) provides: - -- A `verify` function which can prove that some value is part of a - [Merkle tree]. -- A `verify_multi_proof` function which can prove multiple values are part of a - [Merkle tree]. - -[Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree - -*/ - -#![cfg_attr(not(feature = "std"), no_std, no_main)] -extern crate alloc; - -pub mod hash; -pub mod merkle; - -pub mod keccak; -pub use keccak::KeccakBuilder; diff --git a/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs b/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs deleted file mode 100644 index 9e6ee91ca5..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/crypto/src/merkle.rs +++ /dev/null @@ -1,664 +0,0 @@ -//! This module deals with verification of Merkle Tree proofs. -//! -//! The tree and the proofs can be generated using `OpenZeppelin`'s -//! [merkle tree library](https://github.com/OpenZeppelin/merkle-tree). You will -//! find a quickstart guide in its README. -//! -//! WARNING: You should avoid using leaf values that are 64 bytes long -//! prior to hashing, or use a hash function other than keccak256 for -//! hashing leaves. This is because the concatenation of a sorted pair -//! of internal nodes in the Merkle tree could be reinterpreted as a -//! leaf value. `OpenZeppelin`'s JavaScript library generates Merkle trees -//! that are safe against this attack out of the box. -use alloc::vec::Vec; -use core::marker::PhantomData; - -use crate::{ - hash::{commutative_hash_pair, BuildHasher, Hasher}, - KeccakBuilder, -}; - -type Bytes32 = [u8; 32]; - -/// Verify merkle proofs. -pub struct Verifier(PhantomData) -where - B: BuildHasher; - -impl Verifier { - /// Verify that `leaf` is part of a Merkle tree defined by `root` by using - /// `proof` and the default `keccak256` hashing algorithm. - /// - /// A new root is rebuilt by traversing up the Merkle tree. The `proof` - /// provided must contain sibling hashes on the branch starting from the - /// leaf to the root of the tree. Each pair of leaves and each pair of - /// pre-images are assumed to be sorted. - /// - /// A `proof` is valid if and only if the rebuilt hash matches the root - /// of the tree. - /// - /// # Arguments - /// - /// * `proof` - A slice of hashes that constitute the merkle proof. - /// * `root` - The root of the merkle tree, in bytes. - /// * `leaf` - The leaf of the merkle tree to proof, in bytes. - /// - /// # Examples - /// - /// ``` - /// use openzeppelin_crypto::merkle::Verifier; - /// use hex_literal::hex; - /// - /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// - /// let verification = Verifier::verify(&[proof], root, leaf); - /// assert!(!verification); - /// ``` - #[must_use] - pub fn verify(proof: &[Bytes32], root: Bytes32, leaf: Bytes32) -> bool { - Verifier::verify_with_builder(proof, root, leaf, &KeccakBuilder) - } - - /// Verify multiple `leaves` can be simultaneously proven to be a part of - /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` - /// and a `hasher`. - /// - /// The `proof` must contain the sibling hashes one would need to rebuild - /// the root starting from `leaves`. `proof_flags` represents whether a - /// hash must be computed using a `proof` member. A new root is rebuilt by - /// starting from the `leaves` and traversing up the Merkle tree. - /// - /// The procedure incrementally reconstructs all inner nodes by combining - /// a leaf/inner node with either another leaf/inner node or a `proof` - /// sibling node, depending on each proof flag being true or false - /// respectively, i.e., the `i`-th hash must be computed using the proof if - /// `proof_flags[i] == false`. - /// - /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, - /// it is sufficient to ensure that: - /// - The tree is complete (but not necessarily perfect). - /// - The leaves to be proven are in the opposite order they appear in the - /// tree (i.e., as seen from right to left starting at the deepest layer - /// and continuing at the next layer). - /// - /// NOTE: This implementation is *not* equivalent to it's Solidity - /// counterpart. In Rust, access to uninitialized memory panics, which - /// means we don't need to check that the whole proof array has been - /// processed. Both implementations will revert for the same inputs, but - /// for different reasons. See - /// - /// # Arguments - /// - /// * `proof` - A slice of hashes that constitute the merkle proof. - /// * `proof_flags` - A slice of booleans that determine whether to hash - /// leaves or the proof. - /// * `root` - The root of the merkle tree, in bytes. - /// * `leaves` - A slice of hashes that constitute the leaves of the merkle - /// tree to be proven, each leaf in bytes. - /// - /// # Errors - /// - /// Will return `Err` if the arguments are well-formed, but invalid. - /// - /// # Panics - /// - /// Will panic with an out-of-bounds error if the proof is malicious. See - /// - /// - /// # Examples - /// - /// ```rust - /// use openzeppelin_crypto::merkle::Verifier; - /// use hex_literal::hex; - /// - /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); - /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), - /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), - /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; - /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), - /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; - /// - /// let proof_flags = [false, true, false, true]; - /// - /// let verification = - /// Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - /// assert!(verification.unwrap()); - /// ``` - pub fn verify_multi_proof( - proof: &[Bytes32], - proof_flags: &[bool], - root: Bytes32, - leaves: &[Bytes32], - ) -> Result { - Verifier::verify_multi_proof_with_builder( - proof, - proof_flags, - root, - leaves, - &KeccakBuilder, - ) - } -} - -impl Verifier -where - B: BuildHasher, - B::Hasher: Hasher, -{ - /// Verify that `leaf` is part of a Merkle tree defined by `root` by using - /// `proof` and a custom hashing algorithm defined by `builder`. See - /// [`BuildHasher`] for more information on how to construct a builder. - /// - /// WARNING: This is a lower-level function. For most use cases, - /// [`Verifier::verify`], which uses `keccak256` as a hashing algorithm, - /// should be enough. Using other hashing algorithm may have unexpected - /// results. - /// - /// # Arguments - /// - /// * `proof` - A slice of hashes that constitute the merkle proof. - /// * `root` - The root of the merkle tree, in bytes. - /// * `leaf` - The leaf of the merkle tree to proof, in bytes. - /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. - /// - /// # Examples - /// - /// ``` - /// use openzeppelin_crypto::{merkle::Verifier, KeccakBuilder}; - /// use hex_literal::hex; - /// - /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); - /// - /// let verification = Verifier::verify_with_builder(&[proof], root, leaf, &KeccakBuilder); - /// assert!(!verification); - /// ``` - pub fn verify_with_builder( - proof: &[Bytes32], - root: Bytes32, - mut leaf: Bytes32, - builder: &B, - ) -> bool { - for &hash in proof { - leaf = commutative_hash_pair(&leaf, &hash, builder.build_hasher()); - } - - leaf == root - } - - /// Verify multiple `leaves` can be simultaneously proven to be a part of - /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` - /// and a custom hashing algorithm defined by `builder`. See - /// [`BuildHasher`] for more information on how to construct a builder. - /// - /// WARNING: This is a lower-level function. For most use cases, - /// [`Verifier::verify_multi_proof`], which uses `keccak256` as a hashing - /// algorithm, should be enough. Using other hashing algorithm may have - /// unexpected results. - /// - /// The `proof` must contain the sibling hashes one would need to rebuild - /// the root starting from `leaves`. `proof_flags` represents whether a - /// hash must be computed using a `proof` member. A new root is rebuilt by - /// starting from the `leaves` and traversing up the Merkle tree. - /// - /// The procedure incrementally reconstructs all inner nodes by combining - /// a leaf/inner node with either another leaf/inner node or a `proof` - /// sibling node, depending on each proof flag being true or false - /// respectively, i.e., the `i`-th hash must be computed using the proof if - /// `proof_flags[i] == false`. - /// - /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, - /// it is sufficient to ensure that: - /// - The tree is complete (but not necessarily perfect). - /// - The leaves to be proven are in the opposite order they appear in the - /// tree (i.e., as seen from right to left starting at the deepest layer - /// and continuing at the next layer). - /// - /// NOTE: This implementation is *not* equivalent to it's Solidity - /// counterpart. In Rust, access to uninitialized memory panics, which - /// means we don't need to check that the whole proof array has been - /// processed. Both implementations will revert for the same inputs, but - /// for different reasons. See - /// - /// # Arguments - /// - /// * `proof` - A slice of hashes that constitute the merkle proof. - /// * `proof_flags` - A slice of booleans that determine whether to hash - /// leaves or the proof. - /// * `root` - The root of the merkle tree, in bytes. - /// * `leaves` - A slice of hashes that constitute the leaves of the merkle - /// tree to be proven, each leaf in bytes. - /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. - /// - /// # Errors - /// - /// Will return `Err` if the arguments are well-formed, but invalid. - /// - /// # Examples - /// - /// ```rust - /// use openzeppelin_crypto::{merkle::Verifier, KeccakBuilder}; - /// use hex_literal::hex; - /// - /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); - /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), - /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), - /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; - /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), - /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; - /// let proof_flags = [false, true, false, true]; - /// - /// let verification = - /// Verifier::verify_multi_proof_with_builder(&proof, &proof_flags, root, &leaves, &KeccakBuilder); - /// assert!(verification.unwrap()); - /// ``` - pub fn verify_multi_proof_with_builder( - proof: &[Bytes32], - proof_flags: &[bool], - root: Bytes32, - leaves: &[Bytes32], - builder: &B, - ) -> Result { - let total_hashes = proof_flags.len(); - if leaves.len() + proof.len() != total_hashes + 1 { - return Err(MultiProofError::InvalidTotalHashes); - } - if total_hashes == 0 { - // We can safely assume that either `leaves` or `proof` is not empty - // given the previous check. We use `unwrap_or_else` to avoid - // eagerly evaluating `proof[0]`, which may panic. - let rebuilt_root = *leaves.first().unwrap_or_else(|| &proof[0]); - return Ok(root == rebuilt_root); - } - - // `hashes` represents a queue of hashes, our "main queue". - let mut hashes = Vec::with_capacity(total_hashes + leaves.len()); - // Which initially gets populated with the leaves. - hashes.extend(leaves); - // The `xxx_pos` values are "pointers" to the next value to consume in - // each queue. We use them to mimic a queue's pop operation. - let mut proof_pos = 0; - let mut hashes_pos = 0; - // At each step, we compute the next hash using two values: - // - A value from the "main queue". Consume all the leaves, then all the - // hashes but the root. - // - A value from the "main queue" (merging branches) or a member of the - // `proof`, depending on `flag`. - for &flag in proof_flags { - let a = hashes[hashes_pos]; - hashes_pos += 1; - - let b; - if flag { - b = hashes - .get(hashes_pos) - .ok_or(MultiProofError::InvalidRootChild)?; - hashes_pos += 1; - } else { - b = proof - .get(proof_pos) - .ok_or(MultiProofError::InvalidProofLength)?; - proof_pos += 1; - }; - - let hash = commutative_hash_pair(&a, b, builder.build_hasher()); - hashes.push(hash); - } - - // We know that `total_hashes > 0`. - let rebuilt_root = hashes[total_hashes + leaves.len() - 1]; - Ok(root == rebuilt_root) - } -} - -/// An error that occurred while verifying a multi-proof. -/// -/// TODO: Once is resolved, -/// we should derive `core::error::Error`. -#[derive(core::fmt::Debug)] -pub enum MultiProofError { - /// The proof length does not match the flags. - InvalidProofLength, - /// Tried to access uninitialized memory. - /// - /// This happens when the proof is too long, which makes the verification - /// procedure try to access uninitialized memory, which may result in an - /// invalid root. - /// - /// For more information see [this vulnerability]. - /// - /// [this vulnerability]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wprv-93r4-jj2p - InvalidRootChild, - /// The number of leaves and proof members does not match the number of - /// hashes necessary to complete the verification. - InvalidTotalHashes, -} - -impl core::fmt::Display for MultiProofError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let msg = match self { - MultiProofError::InvalidProofLength => "invalid multi-proof length", - MultiProofError::InvalidRootChild => "invalid root child generated", - MultiProofError::InvalidTotalHashes => { - "leaves.len() + proof.len() != total_hashes + 1" - } - }; - - write!(f, "{msg}") - } -} - -#[cfg(all(test, feature = "std"))] -mod tests { - //! NOTE: The values used as input for these tests were all generated using - //! https://github.com/OpenZeppelin/merkle-tree. - use hex_literal::hex; - use rand::{thread_rng, RngCore}; - - use super::{Bytes32, KeccakBuilder, Verifier}; - use crate::hash::{commutative_hash_pair, BuildHasher}; - - /// Shorthand for declaring variables converted from a hex literal to a - /// fixed 32-byte slice. - macro_rules! bytes { - ($($var:ident = $hex:literal);* $(;)?) => { - $( - #[allow(non_upper_case_globals)] - const $var: Bytes32 = hex!($hex); - )* - }; - } - - /// Shorthand for converting from an array of hex literals to an array of - /// fixed 32-bytes slices. - macro_rules! bytes_array { - ($($s:literal),* $(,)?) => { - [ - $(hex!($s),)* - ] - }; - } - - #[test] - fn verifies_valid_proofs() { - // ```js - // const merkleTree = StandardMerkleTree.of( - // toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='), - // ['string'], - // ); - // - // const root = merkleTree.root; - // const hash = merkleTree.leafHash(['A']); - // const proof = merkleTree.getProof(['A']); - // ``` - bytes! { - root = "b89eb120147840e813a77109b44063488a346b4ca15686185cf314320560d3f3"; - leaf_a = "6efbf77e320741a027b50f02224545461f97cd83762d5fbfeb894b9eb3287c16"; - leaf_b = "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc"; - }; - let proof = bytes_array! { - "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc", - "1629d3b5b09b30449d258e35bbd09dd5e8a3abb91425ef810dc27eef995f7490", - "633d21baee4bbe5ed5c51ac0c68f7946b8f28d2937f0ca7ef5e1ea9dbda52e7a", - "8a65d3006581737a3bab46d9e4775dbc1821b1ea813d350a13fcd4f15a8942ec", - "d6c3f3e36cd23ba32443f6a687ecea44ebfe2b8759a62cccf7759ec1fb563c76", - "276141cd72b9b81c67f7182ff8a550b76eb96de9248a3ec027ac048c79649115", - }; - - let verification = Verifier::verify(&proof, root, leaf_a); - assert!(verification); - - let builder = KeccakBuilder.build_hasher(); - let no_such_leaf = commutative_hash_pair(&leaf_a, &leaf_b, builder); - let proof = &proof[1..]; - let verification = Verifier::verify(proof, root, no_such_leaf); - assert!(verification); - } - - #[test] - fn rejects_invalid_proofs() { - // ```js - // const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); - // const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']); - // - // const root = correctMerkleTree.root; - // const leaf = correctMerkleTree.leafHash(['a']); - // const proof = otherMerkleTree.getProof(['d']); - // ``` - bytes! { - root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; - leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - proof = "7b0c6cd04b82bfc0e250030a5d2690c52585e0cc6a4f3bc7909d7723b0236ece"; - }; - - let verification = Verifier::verify(&[proof], root, leaf); - assert!(!verification); - } - - #[test] - fn rejects_proofs_with_invalid_length() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); - // - // const root = merkleTree.root; - // const leaf = merkleTree.leafHash(['a']); - // const proof = merkleTree.getProof(['a']); - // ``` - bytes! { - root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; - leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - }; - let proof = bytes_array! { - "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", - "9cf5a63718145ba968a01c1d557020181c5b252f665cf7386d370eddb176517b", - }; - - let bad_proof = &proof[..1]; - let verification = Verifier::verify(bad_proof, root, leaf); - assert!(!verification); - } - - #[test] - fn verifies_valid_multi_proof() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); - // - // const root = merkleTree.root; - // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf')); - // const hashes = leaves.map(e => merkleTree.leafHash(e)); - // ``` - bytes! { - root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; - }; - let leaves = bytes_array! { - "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", - "c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848", - "eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b", - }; - let proof = bytes_array! { - "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e", - "8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f", - }; - - let proof_flags = [false, true, false, true]; - let verification = - Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - assert!(verification.unwrap()); - } - - #[test] - fn rejects_invalid_multi_proof() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); - // const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']); - // - // const root = merkleTree.root; - // const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi')); - // const hashes = leaves.map(e => merkleTree.leafHash(e)); - // ``` - bytes! { - root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; - }; - let leaves = bytes_array! { - "34e6ce3d0d73f6bff2ee1e865833d58e283570976d70b05f45c989ef651ef742", - "aa28358fb75b314c899e16d7975e029d18b4457fd8fd831f2e6c17ffd17a1d7e", - "e0fd7e6916ff95d933525adae392a17e247819ebecc2e63202dfec7005c60560", - }; - let proof = []; - let proof_flags = [true, true]; - - let verification = - Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - assert!(!verification.unwrap()); - } - - #[test] - fn errors_invalid_multi_proof_leaves() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - // - // const root = merkleTree.root; - // const hashA = merkleTree.leafHash(['a']); - // const hashB = merkleTree.leafHash(['b']); - // const hashCD = hashPair( - // ethers.toBeArray(merkleTree.leafHash(['c'])), - // ethers.toBeArray(merkleTree.leafHash(['d'])), - // ); - // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) - // const fill = ethers.randomBytes(32); - // ``` - bytes! { - root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; - hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; - hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; - hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; - }; - - let mut random_bytes = [0u8; 32]; - thread_rng().fill_bytes(&mut random_bytes); - - let fill = Bytes32::from(random_bytes); - let proof = [hash_b, fill, hash_cd]; - let proof_flags = [false, false, false]; - let leaves = [hash_a, hash_e]; - - let verification = - Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - assert!(verification.is_err()); - } - - #[test] - fn errors_multi_proof_len_invalid() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - // - // const root = merkleTree.root; - // const hashA = merkleTree.leafHash(['a']); - // const hashB = merkleTree.leafHash(['b']); - // const hashCD = hashPair( - // ethers.toBeArray(merkleTree.leafHash(['c'])), - // ethers.toBeArray(merkleTree.leafHash(['d'])), - // ); - // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) - // const fill = ethers.randomBytes(32); - // ``` - bytes! { - root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; - hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; - hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; - hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; - }; - - let mut random_bytes = [0u8; 32]; - thread_rng().fill_bytes(&mut random_bytes); - - let fill = Bytes32::from(random_bytes); - let proof = [hash_b, fill, hash_cd]; - let proof_flags = [false, false, false, false]; - let leaves = [hash_e, hash_a]; - - let verification = - Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - assert!(verification.is_err()); - } - - #[test] - fn verifies_single_leaf_multi_proof() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']); - // - // const root = merkleTree.root; - // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a')); - // const hashes = leaves.map(e => merkleTree.leafHash(e)); - // ``` - bytes!(root = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"); - let proof = []; - let proof_flags = []; - let leaves = [root]; - - let verification = Verifier::::verify_multi_proof( - &proof, - &proof_flags, - root, - &leaves, - ); - assert!(verification.unwrap()); - } - - #[test] - fn verifies_empty_leaves_multi_proof() { - // ```js - // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - // - // const root = merkleTree.root; - // ``` - bytes!(root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"); - let proof = [root]; - let proof_flags = []; - let leaves = []; - - let verification = - Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); - assert!(verification.unwrap()); - } - - #[test] - /// Errors when processing manipulated proofs with a zero-value node at - /// depth 1. - fn errors_manipulated_multi_proof() { - // ```js - // // Create a merkle tree that contains a zero leaf at depth 1 - // const leave = ethers.id('real leaf'); - // const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0)); - // - // // Now we can pass any **malicious** fake leaves as valid! - // const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id) - // .map(ethers.toBeArray).sort(Buffer.compare); - // const maliciousProof = [leave, leave]; - // const maliciousProofFlags = [true, true, false]; - // ``` - bytes! { - root = "f2d552e1e4c59d4f0fa2b80859febc9e4bdc915dff37c56c858550d8b64659a5"; - leaf = "5e941ddd8f313c0b39f92562c0eca709c3d91360965d396aaef584b3fa76889a"; - }; - let malicious_leaves = bytes_array! { - "1f23ad5fc0ee6ccbe2f3d30df856758f05ad9d03408a51a99c1c9f0854309db2", - "613994f4e324d0667c07857cd5d147994bc917da5d07ee63fc3f0a1fe8a18e34", - }; - let malicious_proof = [leaf, leaf]; - let malicious_proof_flags = [true, true, false]; - - let verification = Verifier::verify_multi_proof( - &malicious_proof, - &malicious_proof_flags, - root, - &malicious_leaves, - ); - assert!(verification.is_err()); - } -} From 2ca86385fdeace02ec27c27658a15e560a212f17 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:05:55 +0000 Subject: [PATCH 095/160] Delete target_chains/ethereum/sdk/stylus/.linkspector.yml --- target_chains/ethereum/sdk/stylus/.linkspector.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/.linkspector.yml diff --git a/target_chains/ethereum/sdk/stylus/.linkspector.yml b/target_chains/ethereum/sdk/stylus/.linkspector.yml deleted file mode 100644 index 5f26c71861..0000000000 --- a/target_chains/ethereum/sdk/stylus/.linkspector.yml +++ /dev/null @@ -1,7 +0,0 @@ -dirs: - - docs/modules/ROOT/pages/ - -# There's a bug in linkspector that causes it to only allow one file extension type -# when using it with directories. -fileExtensions: - - adoc From 4ee4ccf9c8cd985c2c1d8cc7c12f267625e852fe Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:06:27 +0000 Subject: [PATCH 096/160] Delete target_chains/ethereum/sdk/stylus/netlify.toml --- target_chains/ethereum/sdk/stylus/netlify.toml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/netlify.toml diff --git a/target_chains/ethereum/sdk/stylus/netlify.toml b/target_chains/ethereum/sdk/stylus/netlify.toml deleted file mode 100644 index 581c663645..0000000000 --- a/target_chains/ethereum/sdk/stylus/netlify.toml +++ /dev/null @@ -1,4 +0,0 @@ -[build] -base = "docs/" -command = "npm run docs" -publish = "build/site" From bf3199d06e5d5c0bd810b0bf2d8d504a6c467a2d Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:07:20 +0000 Subject: [PATCH 097/160] Update Cargo.toml removed openzepline crypto --- target_chains/ethereum/sdk/stylus/Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 77876b2afe..5142b539b9 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "contracts", - "lib/crypto", "lib/motsu", "lib/motsu-proc", "lib/e2e", @@ -12,7 +11,6 @@ members = [ ] default-members = [ "contracts", - "lib/crypto", "lib/motsu", "lib/motsu-proc", "lib/e2e-proc", @@ -83,7 +81,6 @@ proc-macro2 = "1.0.79" quote = "1.0.35" # members -openzeppelin-crypto = { path = "lib/crypto" } motsu = { path = "lib/motsu"} motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } e2e = { path = "lib/e2e" } @@ -102,4 +99,4 @@ debug-assertions = false incremental = false [profile.dev] -panic = "abort" \ No newline at end of file +panic = "abort" From a74751d177342619b6e1087085037723df5f512d Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:09:13 +0000 Subject: [PATCH 098/160] Update README.md --- target_chains/ethereum/sdk/stylus/pyth-solidity/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md b/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md index 9265b45584..a67dd3fc88 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md @@ -1,11 +1,12 @@ ## Foundry -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +**The goal of the folder is to deploy a mock solidity pyth contract using the pyth sdk** + Foundry consists of: -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Forge**: Ethereum testing framework (like Truffle, Hardhat, and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions, and getting chain data. - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. - **Chisel**: Fast, utilitarian, and verbose solidity REPL. From 3ed5a924ff5b377a061dab7c85a50e837fefe5ec Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:09:34 +0000 Subject: [PATCH 099/160] Delete target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example --- target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example | 1 - 1 file changed, 1 deletion(-) delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example b/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example deleted file mode 100644 index 937ce56fd5..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/.env.example +++ /dev/null @@ -1 +0,0 @@ -RPC="http://localhost:8545" \ No newline at end of file From 7d2f445e0c927188c0dcb6050b97e6b65cc153f8 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Sat, 9 Nov 2024 00:12:58 +0000 Subject: [PATCH 100/160] Update gas-bench.yml --- .../ethereum/sdk/stylus/.github/workflows/gas-bench.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml index a14bf25c3a..f269bfb68d 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml @@ -30,6 +30,9 @@ jobs: - uses: Swatinem/rust-cache@v2 with: key: "gas-bench" + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 - name: install cargo-stylus run: cargo install cargo-stylus@0.5.1 From 5c2b2c0633abc3b8de04561606a3569bec0a71a8 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 9 Nov 2024 01:17:51 +0000 Subject: [PATCH 101/160] chore:removed a bunch of openzeplin files --- .../stylus/.github/pull_request_template.md | 21 -- target_chains/ethereum/sdk/stylus/Cargo.lock | 10 - .../ethereum/sdk/stylus/GUIDELINES.md | 328 ------------------ 3 files changed, 359 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/.github/pull_request_template.md delete mode 100644 target_chains/ethereum/sdk/stylus/GUIDELINES.md diff --git a/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md b/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md deleted file mode 100644 index 385188f3ab..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/pull_request_template.md +++ /dev/null @@ -1,21 +0,0 @@ - - - -Resolves #??? - -#### PR Checklist - - - -- [ ] Tests -- [ ] Documentation diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index fbcea20ef0..0b080db4e8 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2447,16 +2447,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "openzeppelin-crypto" -version = "0.1.0" -dependencies = [ - "hex-literal", - "mini-alloc", - "rand", - "tiny-keccak", -] - [[package]] name = "owo-colors" version = "4.0.0" diff --git a/target_chains/ethereum/sdk/stylus/GUIDELINES.md b/target_chains/ethereum/sdk/stylus/GUIDELINES.md deleted file mode 100644 index 2772b3a566..0000000000 --- a/target_chains/ethereum/sdk/stylus/GUIDELINES.md +++ /dev/null @@ -1,328 +0,0 @@ -# Engineering Guidelines - -## Testing - -Code must be thoroughly tested with quality unit tests. - -We defer to the [Moloch Testing Guide] for specific recommendations, though -not all of it is relevant here, since this is a Rust project. Note the -introduction: - -> Tests should be written, not only to verify correctness of the target code, -> but to be comprehensively reviewed by other programmers. Therefore, for -> mission critical Solidity code, the quality of the tests are just as -> important (if not more so) than the code itself, and should be written with -> the highest standards of clarity and elegance. - -Every addition or change to the code must come with relevant and comprehensive -tests. - -Refactors should avoid simultaneous changes to tests. - -Flaky tests are not acceptable. - -The test suite should run automatically for every change in the repository, and -in pull requests tests must pass before merging. - -The test suite coverage must be kept as close to 100% as possible, enforced in -pull requests. - -In some cases unit tests may be insufficient and complementary techniques -should be used: - -1. Property-based tests (aka. fuzzing) for math-heavy code. -2. Formal verification for state machines. - -[Moloch Testing Guide]: https://github.com/MolochVentures/moloch/tree/master/test#readme - -## Peer review - -All changes must be submitted through pull requests and go through peer code -review. - -The review must be approached by the reviewer in a similar way as if it was an -audit of the code in question (but importantly it is not a substitute for and -should not be considered an audit). - -Reviewers should enforce code and project guidelines. - -External contributions must be reviewed separately by multiple maintainers. - -## Code style - -Rust code should be written in a consistent format enforced by `rustfmt`, -following the official [The Rust Style Guide]. See below for further -[Rust Conventions](#rust-conventions). - -The code should be simple and straightforward, prioritizing readability and -understandability. Consistency and predictability should be maintained across -the codebase. In particular, this applies to naming, which should be -systematic, clear, and concise. - -Sometimes these guidelines may be broken if doing so brings significant -efficiency gains, but explanatory comments should be added. - -The following code guidelines will help make code review smoother: - -### Use of `unwrap` and `expect` - -Use `unwrap` only in either of three circumstances: - -- Based on manual static analysis, you've concluded that it's impossible for - the code to panic; so unwrapping is _safe_. An example would be: - -```rust -let list = vec![a, b, c]; -let first = list.first().unwrap(); -``` - -- The panic caused by `unwrap` would indicate a bug in the software, and it - would be impossible to continue in that case. -- The `unwrap` is part of test code, ie. `cfg!(test)` is `true`. - -In the first and second case, document `unwrap` call sites with a comment -prefixed with `SAFETY:` that explains why it's safe to unwrap, eg. - -```rust -// SAFETY: Node IDs are valid ref strings. -let r = RefString::try_from(node.to_string()).unwrap(); -``` - -Use `expect` only if the function expects certain invariants that were not met, -either due to bad inputs, or a problem with the environment; and include the -expectation in the message. For example: - -```rust -logger::init(log::Level::Debug).expect("logger must only be initialized once"); -``` - -### Module imports - -Imports are organized in groups, from least specific to more specific: - -```rust -use std::collections::HashMap; // First, `std` imports. -use std::process; -use std::time; - -use git_ref_format as format; // Then, external dependencies. -use once_cell::sync::Lazy; - -use crate::crypto::PublicKey; // Finally, local crate imports. -use crate::storage::refs::Refs; -use crate::storage::RemoteId; -``` - -This is enforced by `rustfmt`. Note that this is a `nightly` feature. - -### Variable naming - -Use short 1-letter names when the variable scope is only a few lines, or the -context is -obvious, eg. - -```rust -if let Some(e) = result.err() { -... -} -``` - -Use 1-word names for function parameters or variables that have larger scopes: - -```rust -pub fn commit(repo: &Repository, sig: &Signature) -> Result { - ... -} -``` - -Use the most descriptive names for globals: - -```rust -pub const KEEP_ALIVE_DELTA: LocalDuration = LocalDuration::from_secs(30); -``` - -### Function naming - -Stay concise. Use the function doc comment to describe what the function does, -not the name. Keep in mind functions are in the context of the parent module -and/or object and repeating that would be redundant. - -## Dependencies - -Before adding any code dependencies, check with the maintainers if this is -okay. In general, we try not to add external dependencies unless it's -necessary. Dependencies increase counter-party risk, build-time, attack -surface, and make code harder to audit. - -We also optimize for binary size, which means we try to keep generated code to -a minimum and adding dependencies is one of the biggest sources of code bloat. - -## Documentation - -For contributors, project guidelines and processes must be documented publicly. - -For users, features must be abundantly documented. Documentation should include -answers to common questions, solutions to common problems, and recommendations -for critical decisions that the user may face. - -All changes to the core codebase (excluding tests, auxiliary scripts, etc.) -must be documented in a changelog, except for purely cosmetic or documentation -changes. - -All Rust items must be documented with documentation comments so that LSPs -display information about said items. - -## Automation - -Automation should be used as much as possible to reduce the possibility of -human error and forgetfulness. - -Automations that make use of sensitive credentials must use secure secret -management, and must be strengthened against attacks such as -[those on GitHub Actions worklows]. - -Some other examples of automation are: - -- Looking for common security vulnerabilities or errors in our code (eg. - reentrancy analysis). -- Keeping dependencies up to date and monitoring for vulnerable dependencies. - -[those on GitHub Actions worklows]: https://github.com/nikitastupin/pwnhub - -### Linting & formatting - -Always check your code with the linter (`clippy`), by running: - - $ cargo clippy --tests --all-features - -And make sure your code is formatted with, using: - - $ cargo +nightly fmt - -Finally, ensure there is no trailing whitespace anywhere. - -### Running tests - -Make sure all tests are passing with: - - $ cargo test --all-features - -### Running end-to-end tests - -In order to run end-to-end (e2e) tests you need to have a specific nightly toolchain. -"Nightly" is necessary to use optimization compiler flags and have contract wasm small enough to be eligible for -deployment. - -Run the following commands to install the necessary toolchain: - -```shell -rustup install nightly-2024-01-01 -rustup component add rust-src -``` - -Also, you should have the cargo stylus tool: - -```shell -cargo install cargo-stylus -``` - -Since most of the e2e tests use [koba](https://github.com/OpenZeppelin/koba) for deploying contracts, you need to -[install](https://docs.soliditylang.org/en/latest/installing-solidity.html#) the solidity compiler (`v0.8.24`). - -To run e2e tests, you need to have a local nitro test node up and running. -Run the following command and wait till script exit successfully: - -```shell -./scripts/nitro-testnode.sh -i -d -``` - -Then you will be able to run e2e tests: - -```shell -./scripts/e2e-tests.sh -``` - -### Checking the docs - -If you make documentation changes, you may want to check whether there are any -warnings or errors: - - $ cargo doc --all-features - -## Pull requests - -Pull requests are squash-merged to keep the `main` branch history clean. The -title of the pull request becomes the commit message, which should follow -[Semantic versioning]. - -Work in progress pull requests should be submitted as Drafts and should not be -prefixed with "WIP:". - -Branch names don't matter, and commit messages within a pull request mostly -don't matter either, although they can help the review process. - -## Writing commit messages - -A properly formed git commit subject line should always be able to complete the -following sentence: - - If applied, this commit will _____ - -In addition, it should be not capitalized and _must not_ include a period. We -prefix all commits by following [Conventional Commits] guidelines. For example, -the following message is well formed: - - feat(merkle): add single-leaf proof verification - -While these ones are **not**: `add single-leaf proof verification`, -`Added single-leaf proof verification`, `Add single-leaf proof verification`, -`frob: add single-leaf proof verification`, `feat: Add single-leaf proof -verification`. - -When it comes to formatting, here's a model git commit message[1]: - - type: lower-case, short (50 chars or less) summary - - More detailed explanatory text, if necessary. Wrap it to about 72 - characters or so. In some contexts, the first line is treated as the - subject of an email and the rest of the text as the body. The blank - line separating the summary from the body is critical (unless you omit - the body entirely); tools like rebase can get confused if you run the - two together. - - Write your commit message in the imperative: "Fix bug" and not "Fixed bug" - or "Fixes bug." This convention matches up with commit messages generated - by commands like git merge and git revert. - - Further paragraphs come after blank lines. - - - Bullet points are okay, too. - - Typically a hyphen or asterisk is used for the bullet, followed by a - single space, with blank lines in between, but conventions vary here. - - Use a hanging indent. - -## Rust Conventions - -In addition to the official [The Rust Style Guide] we have a number of other -conventions that must be followed. - -- All arithmetic should be checked, matching Solidity's behavior, unless - overflow/underflow is guaranteed not to happen. Unchecked arithmetic blocks - should contain comments explaining why overflow is guaranteed not to happen. - If the reason is immediately apparent from the line above the unchecked - block, the comment may be omitted. -- Custom errors should be declared following the [EIP-6093] rationale whenever - reasonable. Also, consider the following: - - - The domain prefix should be picked in the following order: - 1. Use `ERC` if the error is a violation of an ERC specification. - 2. Use the name of the underlying component where it belongs (eg. - `Governor`, `ECDSA`, or `Timelock`). - -[The Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/ - -[EIP-6093]: https://eips.ethereum.org/EIPS/eip-6093 - -[Semantic versioning]: https://semver.org/spec/v2.0.0.html - -[Conventional Commits]: https://www.conventionalcommits.org/en/v1.0.0/ From 038015bb11ed3a11964564a8c7c4447251ca3a13 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 9 Nov 2024 01:44:16 +0000 Subject: [PATCH 102/160] chore: removed workflow --- .../ethereum/sdk/stylus/.github/codecov.yml | 26 ---- .../sdk/stylus/.github/dependabot.yml | 15 --- .../stylus/.github/workflows/check-links.yml | 37 ------ .../.github/workflows/check-publish.yml | 44 ------- .../stylus/.github/workflows/check-wasm.yml | 38 ------ .../sdk/stylus/.github/workflows/check.yml | 113 ---------------- .../stylus/.github/workflows/e2e-tests.yml | 60 --------- .../stylus/.github/workflows/gas-bench.yml | 51 -------- .../sdk/stylus/.github/workflows/nostd.yml | 32 ----- .../sdk/stylus/.github/workflows/test.yml | 123 ------------------ 10 files changed, 539 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/.github/codecov.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/dependabot.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/test.yml diff --git a/target_chains/ethereum/sdk/stylus/.github/codecov.yml b/target_chains/ethereum/sdk/stylus/.github/codecov.yml deleted file mode 100644 index e4802e57a2..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/codecov.yml +++ /dev/null @@ -1,26 +0,0 @@ -# ref: https://docs.codecov.com/docs/codecovyml-reference -coverage: - # Hold ourselves to a high bar. - range: 95..100 - round: down - precision: 1 - status: - # ref: https://docs.codecov.com/docs/commit-status - project: - default: - # Avoid false negatives. - threshold: 1% - informational: true - patch: - default: - informational: true -# Test files aren't important for coverage. -ignore: - - "tests" - - "docs" -# Make comments less noisy. -comment: - layout: "files" - require_changes: true -github_checks: - annotations: false diff --git a/target_chains/ethereum/sdk/stylus/.github/dependabot.yml b/target_chains/ethereum/sdk/stylus/.github/dependabot.yml deleted file mode 100644 index 4649a6e9b9..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: daily - - package-ecosystem: cargo - directory: / - schedule: - interval: daily - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-patch" - - "version-update:semver-minor" diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml deleted file mode 100644 index 17b354f1fa..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Check Links -# This workflow checks that all links in the documentation are valid. -# It does this for antora docs(adoc) and markdown files. -# We prefer lycheeverse because it is faster, but doesn't support adoc files yet(https://github.com/lycheeverse/lychee/issues/291) -# Because of that, we use linkspector for adoc files and lychee for md files. -on: - push: - branches: [ main, v* ] - pull_request: - branches: [ main, v* ] - -jobs: - check-links-md: - name: Check Markdown Links - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Link Checker - uses: lycheeverse/lychee-action@v2 - with: - args: --no-progress './**/*.md' - fail: true - - check-links-adoc: - name: Check AsciiDoc Links - runs-on: ubuntu-latest - # We only run the AsciiDoc link checker on v* branches because: - # 1. The main branch may contain documentation that is not yet published. - if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') - steps: - - uses: actions/checkout@v4 - - - name: Run linkspector - uses: umbrelladocs/action-linkspector@v1 - with: - fail_on_error: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml deleted file mode 100644 index 16956b9fd0..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check-publish.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: check-publish -# This workflow checks if the libraries can be published on crates.io. -permissions: - contents: read -on: - push: - branches: [ main ] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - check-publish: - name: Check publish on crates.io - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: set up rust - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - target: wasm32-unknown-unknown - components: rust-src - toolchain: nightly-2024-01-01 - - - uses: Swatinem/rust-cache@v2 - - - name: check motsu-proc - run: cargo publish -p motsu-proc --dry-run - - - name: check motsu - run: cargo publish -p motsu --dry-run - - - name: check openzeppelin-crypto - run: cargo publish -p openzeppelin-crypto --target wasm32-unknown-unknown --dry-run - - - name: check openzeppelin-stylus-proc - run: cargo publish -p openzeppelin-stylus-proc --target wasm32-unknown-unknown --dry-run - - - name: check openzeppelin-stylus - run: cargo publish -p openzeppelin-stylus --target wasm32-unknown-unknown --dry-run diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml deleted file mode 100644 index f452c4fa20..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: check-wasm -# This workflow checks that the compiled wasm binary of every example contract -# can be deployed to Arbitrum Stylus. -permissions: - contents: read -on: - push: - branches: [ main ] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - check-wasm: - name: Check WASM binary - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: set up rust - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - target: wasm32-unknown-unknown - components: rust-src - toolchain: nightly-2024-01-01 - - - uses: Swatinem/rust-cache@v2 - - - name: install cargo-stylus - run: cargo install cargo-stylus@0.5.1 - - - name: run wasm check - run: | - export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} - ./scripts/check-wasm.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml deleted file mode 100644 index a8ee0f6005..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: check -# This workflow runs whenever a PR is opened or updated, or a commit is pushed -# to main. It runs several checks: -# - fmt: checks that the code is formatted according to `rustfmt`. -# - clippy: checks that the code does not contain any `clippy` warnings. -# - doc: checks that the code can be documented without errors. -# - hack: check combinations of feature flags. -# - typos: checks for typos across the repo. -permissions: - contents: read -# This configuration allows maintainers of this repo to create a branch and -# pull request based on the new branch. Restricting the push trigger to the -# main branch ensures that the PR only gets built once. -on: - push: - branches: [ main ] - pull_request: -# If new code is pushed to a PR branch, then cancel in progress workflows for -# that PR. Ensures that we don't waste CI time, and returns results quicker. -# https://github.com/jonhoo/rust-ci-conf/pull/5 -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - fmt: - runs-on: ubuntu-latest - name: nightly / fmt - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - # We run in nightly to make use of some features only available there. - # Check out `rustfmt.toml` to see which ones. - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - name: cargo fmt --all --check - run: cargo fmt --all --check - clippy: - runs-on: ubuntu-latest - name: ${{ matrix.toolchain }} / clippy - permissions: - contents: read - checks: write - strategy: - fail-fast: false - matrix: - # Get early warning of new lints which are regularly introduced in beta - # channels. - toolchain: [ stable, beta ] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install ${{ matrix.toolchain }} - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - components: clippy - - name: cargo clippy - uses: giraffate/clippy-action@v1 - with: - reporter: 'github-pr-check' - github_token: ${{ secrets.GITHUB_TOKEN }} - doc: - # Run docs generation on nightly rather than stable. This enables features - # like https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html - # which allows an API be documented as only available in some specific - # platforms. - runs-on: ubuntu-latest - name: nightly / doc - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install nightly - uses: dtolnay/rust-toolchain@nightly - - name: cargo doc - run: cargo doc --no-deps --all-features - env: - RUSTDOCFLAGS: --cfg docsrs - hack: - # `cargo-hack` checks combinations of feature flags to ensure that features - # are all additive which is required for feature unification. - runs-on: ubuntu-latest - name: ubuntu / stable / features - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - with: - target: wasm32-unknown-unknown - - name: cargo install cargo-hack - uses: taiki-e/install-action@cargo-hack - # Intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 - # `--feature-powerset` runs for every combination of features. Note that - # target in this context means one of `--lib`, `--bin`, etc, and not the - # target triple. - - name: cargo hack - run: cargo hack check --feature-powerset --depth 2 --release --target wasm32-unknown-unknown --skip std --workspace --exclude e2e --exclude basic-example-script --exclude benches - typos: - runs-on: ubuntu-latest - name: ubuntu / stable / typos - steps: - - name: Checkout Actions Repository - uses: actions/checkout@v4 - - name: Check spelling of files in the workspace - uses: crate-ci/typos@v1.26.0 diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml deleted file mode 100644 index aa3c27b567..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This workflow runs our end-to-end tests suite. -# -# It roughly follows these steps: -# - Install Rust -# - Install `cargo-stylus` -# - Install `solc` -# - Spin up `nitro-testnode` -# -# Contract deployments and account funding happen on a per-test basis. -name: e2e -permissions: - contents: read -on: - push: - branches: [ main ] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - required: - name: tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: set up rust - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - target: wasm32-unknown-unknown - components: rust-src - toolchain: nightly-2024-01-01 - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - uses: Swatinem/rust-cache@v2 - with: - key: "e2e-tests" - - - name: install cargo-stylus - run: cargo install cargo-stylus@0.5.1 - - - name: install solc - run: | - curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux - sudo mv solc-static-linux /usr/bin/solc - sudo chmod a+x /usr/bin/solc - - - name: setup nitro node - run: ./scripts/nitro-testnode.sh -d -i - - name: run integration tests - run: | - export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} - ./scripts/e2e-tests.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml deleted file mode 100644 index f269bfb68d..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: gas-bench -# This workflow checks that the compiled wasm binary of every example contract -# can be deployed to Arbitrum Stylus. -permissions: - contents: read -on: - push: - branches: [ main ] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - required: - name: gas usage report - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: set up rust - uses: dtolnay/rust-toolchain@master - id: toolchain - with: - target: wasm32-unknown-unknown - components: rust-src - toolchain: nightly-2024-01-01 - - - uses: Swatinem/rust-cache@v2 - with: - key: "gas-bench" - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: install cargo-stylus - run: cargo install cargo-stylus@0.5.1 - - - name: install solc - run: | - curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux - sudo mv solc-static-linux /usr/bin/solc - sudo chmod a+x /usr/bin/solc - - - name: setup nitro node - run: ./scripts/nitro-testnode.sh -d -i - - name: run benches - run: | - export NIGHTLY_TOOLCHAIN=${{steps.toolchain.outputs.name}} - ./scripts/bench.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml deleted file mode 100644 index a9e1c6e819..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflow checks whether the library is able to run without the std -# library. See `check.yml` for information about how the concurrency -# cancellation and workflow triggering works. -name: no-std -permissions: - contents: read -on: - push: - branches: [ main ] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - nostd: - runs-on: ubuntu-latest - name: ${{ matrix.target }} - strategy: - matrix: - target: [ wasm32-unknown-unknown ] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: rustup target add ${{ matrix.target }} - run: rustup target add ${{ matrix.target }} - - name: cargo check - run: cargo check --release --target ${{ matrix.target }} --no-default-features diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml deleted file mode 100644 index 879ec46c03..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/test.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: test -# This is the main CI workflow that runs the test suite on all pushes to main -# and all pull requests. It runs the following jobs: -# - required: runs the test suite on ubuntu with stable and beta rust -# toolchains. -# - os-check: runs the test suite on mac and windows. -# - coverage: runs the test suite and collects coverage information. -# See `check.yml` for information about how the concurrency cancellation and -# workflow triggering works. -permissions: - contents: read -on: - push: - branches: [ main ] - paths-ignore: - - "**.md" - - "**.adoc" - pull_request: - paths-ignore: - - "**.md" - - "**.adoc" -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -env: - CARGO_TERM_COLOR: always -jobs: - required: - runs-on: ubuntu-latest - name: ubuntu / ${{ matrix.toolchain }} - strategy: - matrix: - # Run on stable and beta to ensure that tests won't break on the next - # version of the rust toolchain. - toolchain: [ stable, beta ] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install ${{ matrix.toolchain }} - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - - name: cargo generate-lockfile - # Enable this ci template to run regardless of whether the lockfile is - # checked in or not. - if: hashFiles('Cargo.lock') == '' - run: cargo generate-lockfile - # https://twitter.com/jonhoo/status/1571290371124260865 - - name: cargo test --locked - run: cargo test --locked --features std --all-targets - # https://github.com/rust-lang/cargo/issues/6669 - - name: cargo test --doc - run: cargo test --locked --features std --doc - os-check: - # Run cargo test on MacOS and Windows. - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} / stable - strategy: - fail-fast: false - matrix: - os: [ macos-latest ] - # Windows fails because of `stylus-proc`. - # os: [macos-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - - name: cargo generate-lockfile - if: hashFiles('Cargo.lock') == '' - run: cargo generate-lockfile - - name: cargo test - run: cargo test --locked --features std --all-targets - coverage: - # Use llvm-cov to build and collect coverage and outputs in a format that - # is compatible with codecov.io. - # - # Note that codecov as of v4 requires that CODECOV_TOKEN from - # - # https://app.codecov.io/gh///settings - # - # is set in two places on your repo: - # - # - https://github.com/jonhoo/guardian/settings/secrets/actions - # - https://github.com/jonhoo/guardian/settings/secrets/dependabot - # - # (the former is needed for codecov uploads to work with Dependabot PRs) - # - # PRs coming from forks of your repo will not have access to the token, but - # for those, codecov allows uploading coverage reports without a token. - # it's all a little weird and inconvenient. see - # - # https://github.com/codecov/feedback/issues/112 - # - # for lots of more discussion. - runs-on: ubuntu-latest - name: ubuntu / stable / coverage - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install stable - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools-preview - - name: cargo install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: cargo generate-lockfile - if: hashFiles('Cargo.lock') == '' - run: cargo generate-lockfile - - name: cargo llvm-cov - # FIXME: Include e2e tests in coverage. - run: cargo llvm-cov --locked --features std --lcov --output-path lcov.info - - name: Record Rust version - run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" - - name: Upload to codecov.io - uses: codecov/codecov-action@v4 - with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} - env_vars: OS,RUST From 013ea47e57599fec29835c19aadc7aaa3559f371 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 9 Nov 2024 02:20:46 +0000 Subject: [PATCH 103/160] chore:Added Guidlines --- .../ethereum/sdk/stylus/GUIDELINES.md | 295 ++++++++++++++++++ .../sdk/stylus/benches/src/function_calls.rs | 2 +- .../sdk/stylus/benches/src/proxy_calls.rs | 20 +- 3 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/GUIDELINES.md diff --git a/target_chains/ethereum/sdk/stylus/GUIDELINES.md b/target_chains/ethereum/sdk/stylus/GUIDELINES.md new file mode 100644 index 0000000000..c60f07f49a --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/GUIDELINES.md @@ -0,0 +1,295 @@ +# Engineering Guidelines + +## Testing + +Code must be thoroughly tested with quality unit tests. + +We defer to the [Moloch Testing Guide] for specific recommendations, though +not all of it is relevant here, since this is a Rust project. Note the +introduction: + +> Tests should be written, not only to verify correctness of the target code, +> but to be comprehensively reviewed by other programmers. Therefore, for +> mission critical Solidity code, the quality of the tests are just as +> important (if not more so) than the code itself, and should be written with +> the highest standards of clarity and elegance. + +Every addition or change to the code must come with relevant and comprehensive +tests. + +Refactors should avoid simultaneous changes to tests. + +Flaky tests are not acceptable. + +The test suite should run automatically for every change in the repository, and +in pull requests tests must pass before merging. + +The test suite coverage must be kept as close to 100% as possible, enforced in +pull requests. + +In some cases unit tests may be insufficient and complementary techniques +should be used: + +1. Property-based tests (aka. fuzzing) for math-heavy code. +2. Formal verification for state machines. + +[Moloch Testing Guide]: https://github.com/MolochVentures/moloch/tree/master/test#readme + +## Peer review + +All changes must be submitted through pull requests and go through peer code +review. + +The review must be approached by the reviewer in a similar way as if it was an +audit of the code in question (but importantly it is not a substitute for and +should not be considered an audit). + +Reviewers should enforce code and project guidelines. + +External contributions must be reviewed separately by multiple maintainers. + +## Code style + +Rust code should be written in a consistent format enforced by `rustfmt`, +following the official [The Rust Style Guide]. See below for further +[Rust Conventions](#rust-conventions). + +The code should be simple and straightforward, prioritizing readability and +understandability. Consistency and predictability should be maintained across +the codebase. In particular, this applies to naming, which should be +systematic, clear, and concise. + +Sometimes these guidelines may be broken if doing so brings significant +efficiency gains, but explanatory comments should be added. + +The following code guidelines will help make code review smoother: + +### Use of `unwrap` and `expect` + +Use `unwrap` only in either of three circumstances: + +- Based on manual static analysis, you've concluded that it's impossible for + the code to panic; so unwrapping is _safe_. An example would be: + +```rust +let list = vec![a, b, c]; +let first = list.first().unwrap(); +``` + +- The panic caused by `unwrap` would indicate a bug in the software, and it + would be impossible to continue in that case. +- The `unwrap` is part of test code, ie. `cfg!(test)` is `true`. + +In the first and second case, document `unwrap` call sites with a comment +prefixed with `SAFETY:` that explains why it's safe to unwrap, eg. + +```rust +// SAFETY: Node IDs are valid ref strings. +let r = RefString::try_from(node.to_string()).unwrap(); +``` + +Use `expect` only if the function expects certain invariants that were not met, +either due to bad inputs, or a problem with the environment; and include the +expectation in the message. For example: + +```rust +logger::init(log::Level::Debug).expect("logger must only be initialized once"); +``` + +### Module imports + +Imports are organized in groups, from least specific to more specific: + +```rust +use std::collections::HashMap; // First, `std` imports. +use std::process; +use std::time; + +use git_ref_format as format; // Then, external dependencies. +use once_cell::sync::Lazy; + +use crate::crypto::PublicKey; // Finally, local crate imports. +use crate::storage::refs::Refs; +use crate::storage::RemoteId; +``` + +This is enforced by `rustfmt`. Note that this is a `nightly` feature. + +### Variable naming + +Use short 1-letter names when the variable scope is only a few lines, or the +context is +obvious, eg. + +```rust +if let Some(e) = result.err() { +... +} +``` + +Use 1-word names for function parameters or variables that have larger scopes: + +```rust +pub fn commit(repo: &Repository, sig: &Signature) -> Result { + ... +} +``` + +Use the most descriptive names for globals: + +```rust +pub const KEEP_ALIVE_DELTA: LocalDuration = LocalDuration::from_secs(30); +``` + +### Function naming + +Stay concise. Use the function doc comment to describe what the function does, +not the name. Keep in mind functions are in the context of the parent module +and/or object and repeating that would be redundant. + +## Dependencies + +Before adding any code dependencies, check with the maintainers if this is +okay. In general, we try not to add external dependencies unless it's +necessary. Dependencies increase counter-party risk, build-time, attack +surface, and make code harder to audit. + +We also optimize for binary size, which means we try to keep generated code to +a minimum and adding dependencies is one of the biggest sources of code bloat. + +## Documentation + +For contributors, project guidelines and processes must be documented publicly. + +For users, features must be abundantly documented. Documentation should include +answers to common questions, solutions to common problems, and recommendations +for critical decisions that the user may face. + +All changes to the core codebase (excluding tests, auxiliary scripts, etc.) +must be documented in a changelog, except for purely cosmetic or documentation +changes. + +All Rust items must be documented with documentation comments so that LSPs +display information about said items. + +## Automation + +Automation should be used as much as possible to reduce the possibility of +human error and forgetfulness. + +Automations that make use of sensitive credentials must use secure secret +management, and must be strengthened against attacks such as +[those on GitHub Actions worklows]. + +Some other examples of automation are: + +- Looking for common security vulnerabilities or errors in our code (eg. + reentrancy analysis). +- Keeping dependencies up to date and monitoring for vulnerable dependencies. + +[those on GitHub Actions worklows]: https://github.com/nikitastupin/pwnhub + +### Linting & formatting + +Always check your code with the linter (`clippy`), by running: + + $ cargo clippy --tests --all-features + +And make sure your code is formatted with, using: + + $ cargo +nightly fmt + +Finally, ensure there is no trailing whitespace anywhere. + +### Running tests + +Make sure all tests are passing with: + + $ cargo test --all-features + +### Running end-to-end tests + +In order to run end-to-end (e2e) tests, you should have the cargo stylus tool: + +```shell +cargo install cargo-stylus +``` + +Since most of the e2e tests use [koba](https://github.com/OpenZeppelin/koba) for deploying contracts, you need to +[install](https://docs.soliditylang.org/en/latest/installing-solidity.html#) the solidity compiler (`v0.8.24`). + +To run e2e tests, you need to have a local nitro test node up and running. +Run the following command and wait till script exit successfully: + +```shell +./scripts/nitro-testnode.sh -i -d +``` + +Then you will be able to run e2e tests: + +```shell +./scripts/e2e-tests.sh +``` + +### Checking the docs + +If you make documentation changes, you may want to check whether there are any +warnings or errors: + + $ cargo doc --all-features + +## Pull requests + +Pull requests are squash-merged to keep the `main` branch history clean. The +title of the pull request becomes the commit message, which should follow +[Semantic versioning]. + +Work in progress pull requests should be submitted as Drafts and should not be +prefixed with "WIP:". + +Branch names don't matter, and commit messages within a pull request mostly +don't matter either, although they can help the review process. + +## Writing commit messages + +A properly formed git commit subject line should always be able to complete the +following sentence: + + If applied, this commit will _____ + +In addition, it should be not capitalized and _must not_ include a period. We +prefix all commits by following [Conventional Commits] guidelines. For example, +the following message is well formed: + + feat(merkle): add single-leaf proof verification + +While these ones are **not**: `add single-leaf proof verification`, +`Added single-leaf proof verification`, `Add single-leaf proof verification`, +`frob: add single-leaf proof verification`, `feat: Add single-leaf proof +verification`. + +When it comes to formatting, here's a model git commit message[1]: + + type: lower-case, short (50 chars or less) summary + + More detailed explanatory text, if necessary. Wrap it to about 72 + characters or so. In some contexts, the first line is treated as the + subject of an email and the rest of the text as the body. The blank + line separating the summary from the body is critical (unless you omit + the body entirely); tools like rebase can get confused if you run the + two together. + + Write your commit message in the imperative: "Fix bug" and not "Fixed bug" + or "Fixes bug." This convention matches up with commit messages generated + by commands like git merge and git revert. + + Further paragraphs come after blank lines. + + - Bullet points are okay, too. + - Typically a hyphen or asterisk is used for the bullet, followed by a + single space, with blank lines in between, but conventions vary here. + - Use a hanging indent. + + +[The Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/ + diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 2ae5047668..19c3f79c01 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -21,7 +21,7 @@ sol!( function getUpdateFee() external returns (uint256 fee); function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds() external payable; - function updatePriceFeedsIfNecessary() external payable; + function updatePriceFeedsIfNecessary() external payable; } ); diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index abed9a598a..e3921a7e27 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -1,7 +1,7 @@ -use std::{i64, str::FromStr}; +use std::str::FromStr; use alloy::{ - network::{AnyNetwork, EthereumWallet}, primitives::{uint, Address, FixedBytes as TypeFixedBytes, Bytes, U256}, + network::{AnyNetwork, EthereumWallet}, primitives::{uint, Address, FixedBytes as TypeFixedBytes}, providers::ProviderBuilder, sol, sol_types::{ SolCall, SolConstructor} }; @@ -15,14 +15,14 @@ use crate::{ sol!( #[sol(rpc)] contract ProxyCall{ - function getPriceUnsafe(bytes32 id) external; - function getEmaPriceUnsafe(bytes32 id) external; - function getPriceNoOlderThan(bytes32 id, uint age) external; - function getEmaPriceNoOlderThan(bytes32 id, uint age) external; - function getUpdateFee(bytes[] calldata updateData) external returns (uint256); - function getValidTimePeriod() external; - function updatePriceFeeds(bytes[] calldata updateData) external payable; - function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; + function getPriceUnsafe(bytes32 id) external returns (uint8[] price); + function getEmaPriceUnsafe(bytes32 id) external returns (uint8[] price); + function getPriceNoOlderThan(bytes32 id, uint age) external returns (uint8[] price); + function getEmaPriceNoOlderThan(bytes32 id, uint age) external returns (uint8[] price); + function getUpdateFee(bytes[] calldata updateData) external returns (uint256 fee); + function getValidTimePeriod() external returns (uint256 period); + function updatePriceFeeds(bytes[] calldata updateData) external payable; + function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; } ); From 655e55e9340e1b49aa91c7868c77699406c864fb Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 11 Nov 2024 13:27:45 +0000 Subject: [PATCH 104/160] chore: motsu changes --- target_chains/ethereum/sdk/stylus/Cargo.lock | 30 +++++++++++++++++-- target_chains/ethereum/sdk/stylus/Cargo.toml | 4 +-- .../sdk/stylus/contracts/src/pyth/mock.rs | 11 ++----- .../sdk/stylus/contracts/src/pyth/types.rs | 2 +- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 0b080db4e8..79245c4887 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2293,7 +2293,20 @@ name = "motsu" version = "0.1.0" dependencies = [ "const-hex", - "motsu-proc", + "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", + "stylus-sdk", + "tiny-keccak", +] + +[[package]] +name = "motsu" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744174e5011fed86212d90c1120037da87e0c45fee7a5861c2b3105e41283810" +dependencies = [ + "const-hex", + "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "stylus-sdk", "tiny-keccak", @@ -2305,13 +2318,24 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "motsu", + "motsu 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", "stylus-sdk", "syn 2.0.68", ] +[[package]] +name = "motsu-proc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0785317ee15f9a1bc5d761da9d0d1dadd92225cd5a88eed0ec37c54a277fac44" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -2721,7 +2745,7 @@ dependencies = [ "alloy-sol-types", "keccak-const", "mini-alloc", - "motsu", + "motsu 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "stylus-sdk", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 5142b539b9..319d3f3347 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -81,8 +81,8 @@ proc-macro2 = "1.0.79" quote = "1.0.35" # members -motsu = { path = "lib/motsu"} -motsu-proc = { path = "lib/motsu-proc", version = "0.1.0" } +motsu = "0.1.0" +motsu-proc = "0.1.0" e2e = { path = "lib/e2e" } e2e-proc = {path = "lib/e2e-proc"} pyth-stylus ={path ="contracts"} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 550b66347b..5286ad0d4c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -214,17 +214,12 @@ pub fn create_price_feed_update_data_list() -> (Vec, Vec>) #[cfg(all(test, feature = "std"))] mod tests { use alloc::vec; - use alloy_primitives::{address, uint, Address, U64, U256, FixedBytes, fixed_bytes}; - use stylus_sdk::{abi::Bytes, contract, msg::{self, value}}; + use alloy_primitives::{ U256, FixedBytes}; + use stylus_sdk::abi::Bytes; use - crate::pyth::{ - mock::{MockPythContract, DecodeDataType}, - errors::{Error, InvalidArgument}, types::{PriceFeed, Price, StoragePriceFeed} - }; + crate::pyth::mock::{MockPythContract, DecodeDataType}; use alloy_sol_types::SolType; - use std::{println as info, println as warn}; - // Updated constants to use uppercase naming convention const PRICE: i64 = 1000; const CONF: u64 = 1000; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 2a770bb55c..4b620129df 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -214,7 +214,7 @@ impl StoragePriceFeed { #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::{address, FixedBytes, U256}; + use alloy_primitives::{ FixedBytes, U256}; use crate::pyth::types::{PriceFeed, Price, StoragePriceFeed, StoragePrice}; // Updated constants to use uppercase naming convention From 9628334acea67e4009bb89acda8e3a4983f934f4 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:03:05 +0000 Subject: [PATCH 105/160] Create Test.md --- target_chains/ethereum/sdk/stylus/Test.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 target_chains/ethereum/sdk/stylus/Test.md diff --git a/target_chains/ethereum/sdk/stylus/Test.md b/target_chains/ethereum/sdk/stylus/Test.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/Test.md @@ -0,0 +1 @@ + From 3628d14476586e938977646ad9d80575c91f374e Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:07:50 +0000 Subject: [PATCH 106/160] Update Test.md --- target_chains/ethereum/sdk/stylus/Test.md | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/Test.md b/target_chains/ethereum/sdk/stylus/Test.md index 8b13789179..ff536c6251 100644 --- a/target_chains/ethereum/sdk/stylus/Test.md +++ b/target_chains/ethereum/sdk/stylus/Test.md @@ -1 +1,31 @@ +### Running Unit Tests for `pyth-stylus` + +To run all the unit tests for the `pyth-stylus` package with all its features enabled, use the following command: + +```bash +cargo test -p pyth-stylus --all-features +``` + +This command will: +- Target the `pyth-stylus` package specifically (`-p pyth-stylus`). +- Enable **all features** defined in the package during the test run (`--all-features`). + + +### Running End-to-End Tests + +To run the end-to-end tests for `pyth-stylus`, follow these steps: + +1. Start the test node: + + ```bash + ./scripts/nitro-testnode.sh + ``` + +2. Run the end-to-end tests: + + ```bash + ./scripts/e2e-tests.sh + ``` + +These commands will set up the test environment and run the full end-to-end tests. From 859e6e8204bda8bb773bdad367884ea3febf021f Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:08:16 +0000 Subject: [PATCH 107/160] Rename Test.md to TEST.md --- target_chains/ethereum/sdk/stylus/{Test.md => TEST.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename target_chains/ethereum/sdk/stylus/{Test.md => TEST.md} (100%) diff --git a/target_chains/ethereum/sdk/stylus/Test.md b/target_chains/ethereum/sdk/stylus/TEST.md similarity index 100% rename from target_chains/ethereum/sdk/stylus/Test.md rename to target_chains/ethereum/sdk/stylus/TEST.md From 28c3def26c0840359dcf63cb75a64970d358d2ee Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:11:40 +0000 Subject: [PATCH 108/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index e2b59e7516..b863d2e335 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -84,6 +84,12 @@ impl ProxyCallsExample { [MockPyth](./mock.rs) is a mock contract that can be deployed locally to simulate Pyth contract behavior. To set and update price feeds, call `updatePriceFeeds` and provide an array of encoded price feeds as the argument. Encoded price feeds can be created using the `create_price_feed_update_data` function in the mock contract, which is also available in the functions module. + +## Test Documentation + +Check out this link for all the test instructions and detailed setup for the pyth-stylus package. +[Test Documentaion](./TEST.md) + ### Releases We use [Semantic Versioning](https://semver.org/) for our releases. To release a new version of this package and publish it to npm, follow these steps: From 42241a6c3e069f3eee58a347577617760988b122 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:12:36 +0000 Subject: [PATCH 109/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index b863d2e335..fb9192edfe 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -87,8 +87,31 @@ impl ProxyCallsExample { ## Test Documentation -Check out this link for all the test instructions and detailed setup for the pyth-stylus package. -[Test Documentaion](./TEST.md) +### Running Unit Tests for `pyth-stylus` + +To run all the unit tests for the `pyth-stylus` package with all its features enabled, use the following command: + +```bash +cargo test -p pyth-stylus --all-features +``` + +This command will: +- Target the `pyth-stylus` package specifically (`-p pyth-stylus`). +- Enable **all features** defined in the package during the test run (`--all-features`). + + +### Running End-to-End Tests + +To run the end-to-end tests for `pyth-stylus`, follow these steps: + +1. Start the test node: + + ```bash + ./scripts/nitro-testnode.sh + ``` + +2. Run the end-to-end tests: + ### Releases From 45ff3627d39531f21bb671140e0764e0a75c640d Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:13:59 +0000 Subject: [PATCH 110/160] Update README.md --- target_chains/ethereum/sdk/stylus/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index fb9192edfe..aebd52301a 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -111,6 +111,9 @@ To run the end-to-end tests for `pyth-stylus`, follow these steps: ``` 2. Run the end-to-end tests: + ``` + ./scripts/e2e-tests.sh + ``` ### Releases From a118553836ddfb8ee657779d33f4d62dec69a4e7 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 11 Nov 2024 14:17:56 +0000 Subject: [PATCH 111/160] Delete target_chains/ethereum/sdk/stylus/TEST.md --- target_chains/ethereum/sdk/stylus/TEST.md | 31 ----------------------- 1 file changed, 31 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/TEST.md diff --git a/target_chains/ethereum/sdk/stylus/TEST.md b/target_chains/ethereum/sdk/stylus/TEST.md deleted file mode 100644 index ff536c6251..0000000000 --- a/target_chains/ethereum/sdk/stylus/TEST.md +++ /dev/null @@ -1,31 +0,0 @@ -### Running Unit Tests for `pyth-stylus` - -To run all the unit tests for the `pyth-stylus` package with all its features enabled, use the following command: - -```bash -cargo test -p pyth-stylus --all-features -``` - -This command will: -- Target the `pyth-stylus` package specifically (`-p pyth-stylus`). -- Enable **all features** defined in the package during the test run (`--all-features`). - - -### Running End-to-End Tests - -To run the end-to-end tests for `pyth-stylus`, follow these steps: - -1. Start the test node: - - ```bash - ./scripts/nitro-testnode.sh - ``` - -2. Run the end-to-end tests: - - ```bash - ./scripts/e2e-tests.sh - ``` - -These commands will set up the test environment and run the full end-to-end tests. - From 5f5bc5e544f2188937ad9627a3de2698c3f75b53 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 11 Nov 2024 16:07:49 +0000 Subject: [PATCH 112/160] chore: removed mostu --- target_chains/ethereum/sdk/stylus/Cargo.lock | 28 +- target_chains/ethereum/sdk/stylus/Cargo.toml | 4 - .../sdk/stylus/lib/motsu-proc/Cargo.toml | 26 -- .../sdk/stylus/lib/motsu-proc/README.md | 5 - .../sdk/stylus/lib/motsu-proc/src/lib.rs | 57 --- .../sdk/stylus/lib/motsu-proc/src/test.rs | 52 --- .../ethereum/sdk/stylus/lib/motsu/Cargo.toml | 19 - .../ethereum/sdk/stylus/lib/motsu/README.md | 62 --- .../sdk/stylus/lib/motsu/src/context.rs | 45 --- .../ethereum/sdk/stylus/lib/motsu/src/lib.rs | 61 --- .../sdk/stylus/lib/motsu/src/prelude.rs | 6 - .../sdk/stylus/lib/motsu/src/shims.rs | 378 ------------------ .../sdk/stylus/lib/motsu/src/storage.rs | 32 -- 13 files changed, 2 insertions(+), 773 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 79245c4887..950aec6da4 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -2288,17 +2288,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "motsu" -version = "0.1.0" -dependencies = [ - "const-hex", - "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "once_cell", - "stylus-sdk", - "tiny-keccak", -] - [[package]] name = "motsu" version = "0.1.0" @@ -2306,25 +2295,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744174e5011fed86212d90c1120037da87e0c45fee7a5861c2b3105e41283810" dependencies = [ "const-hex", - "motsu-proc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "motsu-proc", "once_cell", "stylus-sdk", "tiny-keccak", ] -[[package]] -name = "motsu-proc" -version = "0.1.0" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "motsu 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2", - "quote", - "stylus-sdk", - "syn 2.0.68", -] - [[package]] name = "motsu-proc" version = "0.1.0" @@ -2745,7 +2721,7 @@ dependencies = [ "alloy-sol-types", "keccak-const", "mini-alloc", - "motsu 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "motsu", "stylus-sdk", ] diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 319d3f3347..3219a43488 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ "contracts", - "lib/motsu", - "lib/motsu-proc", "lib/e2e", "lib/e2e-proc", "examples/function-calls", @@ -11,8 +9,6 @@ members = [ ] default-members = [ "contracts", - "lib/motsu", - "lib/motsu-proc", "lib/e2e-proc", "examples/function-calls", "examples/proxy-calls", diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml deleted file mode 100644 index 99cbdb6d38..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "motsu-proc" -description = "Mostu's Procedural Macros" -edition.workspace = true -categories = ["development-tools::testing", "cryptography::cryptocurrencies"] -keywords = ["arbitrum", "ethereum", "stylus", "unit-tests", "tests"] -license.workspace = true -repository.workspace = true -version = "0.1.0" - -[dependencies] -proc-macro2.workspace = true -quote.workspace = true -syn.workspace = true - -[dev-dependencies] -motsu.workspace = true -alloy-primitives.workspace = true -alloy-sol-types.workspace = true -stylus-sdk.workspace = true - -[lib] -proc-macro = true - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md deleted file mode 100644 index 40d2ef4b13..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Motsu's Procedural Macros - -This crate contains procedural macros used in [`motsu`]. - -[motsu]: ../motsu/README.md diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs deleted file mode 100644 index c58093e86a..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/lib.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Procedural macro definitions used in `motsu`. -use proc_macro::TokenStream; - -/// Shorthand to print nice errors. -/// -/// Note that it's defined before the module declarations. -macro_rules! error { - ($tokens:expr, $($msg:expr),+ $(,)?) => {{ - let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); - return error.to_compile_error().into(); - }}; - (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ - return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) - }}; -} - -mod test; - -/// Defines a unit test that provides access to Stylus' execution context. -/// -/// Internally, this is a thin wrapper over `#[test]` that gives access to -/// affordances like contract storage and `msg::sender`. If you don't need -/// them, you can pass no arguments to the test function or simply use -/// `#[test]` instead of `#[motsu::test]`. -/// -/// # Examples -/// -/// ```rust,ignore -/// #[cfg(test)] -/// mod tests { -/// #[motsu::test] -/// fn reads_balance(contract: Erc20) { -/// let balance = contract.balance_of(Address::ZERO); -/// assert_eq!(U256::ZERO, balance); -/// -/// let owner = msg::sender(); -/// let one = U256::from(1); -/// contract._balances.setter(owner).set(one); -/// let balance = contract.balance_of(owner); -/// assert_eq!(one, balance); -/// } -/// } -/// ``` -/// -/// ```rust,ignore -/// #[cfg(test)] -/// mod tests { -/// #[motsu::test] -/// fn t() { // If no params, it expands to a `#[test]`. -/// ... -/// } -/// } -/// ``` -#[proc_macro_attribute] -pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream { - test::test(&attr, input) -} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs b/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs deleted file mode 100644 index fe4af7d000..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu-proc/src/test.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Defines the `#[motsu::test]` procedural macro. -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, FnArg}; - -/// Defines a unit test that provides access to Stylus' execution context. -/// -/// For more information see [`crate::test`]. -pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { - let item_fn = parse_macro_input!(input as syn::ItemFn); - let attrs = &item_fn.attrs; - let sig = &item_fn.sig; - let fn_name = &sig.ident; - let fn_return_type = &sig.output; - let fn_block = &item_fn.block; - let fn_args = &sig.inputs; - - // If the test function has no params, then it doesn't need access to the - // contract, so it is just a regular test. - if fn_args.is_empty() { - return quote! { - #( #attrs )* - #[test] - fn #fn_name() #fn_return_type { - let _lock = ::motsu::prelude::acquire_storage(); - let res = #fn_block; - ::motsu::prelude::reset_storage(); - res - } - } - .into(); - } - - // We can unwrap because we handle the empty case above. We don't support - // more than one parameter for now, so we skip them. - let arg = fn_args.first().unwrap(); - let FnArg::Typed(arg) = arg else { - error!(arg, "unexpected receiver argument in test signature"); - }; - let contract_arg_binding = &arg.pat; - let contract_ty = &arg.ty; - quote! { - #( #attrs )* - #[test] - fn #fn_name() #fn_return_type { - ::motsu::prelude::with_context::<#contract_ty>(| #contract_arg_binding | - #fn_block - ) - } - } - .into() -} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml deleted file mode 100644 index 5f3e5958ac..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "motsu" -description = "Unit Testing for Stylus" -edition.workspace = true -categories = ["development-tools::testing", "cryptography::cryptocurrencies"] -keywords = ["arbitrum", "ethereum", "stylus", "unit-tests", "tests"] -license.workspace = true -repository.workspace = true -version = "0.1.0" - -[dependencies] -const-hex.workspace = true -once_cell.workspace = true -tiny-keccak.workspace = true -stylus-sdk.workspace = true -motsu-proc.workspace = true - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/README.md b/target_chains/ethereum/sdk/stylus/lib/motsu/README.md deleted file mode 100644 index f7e8c074c0..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Motsu (持つ) - Unit Testing for Stylus - -This crate enables unit-testing for Stylus contracts. It abstracts away the -machinery necessary for writing tests behind a `#[motsu::test]` procedural -macro. - -`motsu` means ["to hold"](https://jisho.org/word/%E6%8C%81%E3%81%A4) in -Japanese -- we hold a stylus in our hand. - -## Usage - -Annotate tests with `#[motsu::test]` instead of `#[test]` to get access to VM -affordances. - -Note that we require contracts to implement `stylus_sdk::prelude::StorageType`. -This trait is typically implemented by default with `stylus_proc::sol_storage` macro. - -```rust -#[cfg(test)] -mod tests { - use contracts::token::erc20::Erc20; - - #[motsu::test] - fn reads_balance(contract: Erc20) { - let balance = contract.balance_of(Address::ZERO); // Access storage. - assert_eq!(balance, U256::ZERO); - } -} -``` - -Annotating a test function that accepts no parameters will make `#[motsu::test]` -behave the same as `#[test]`. - -```rust,ignore -#[cfg(test)] -mod tests { - #[motsu::test] - fn t() { // If no params, it expands to a `#[test]`. - // ... - } -} -``` - -Note that currently, test suites using `motsu::test` will run serially because -of global access to storage. - -### Notice - -We maintain this crate on a best-effort basis. We use it extensively on our own -tests, so we will add here any symbols we may need. However, since we expect -this to be a temporary solution, don't expect us to address all requests. - -That being said, please do open an issue to start a discussion, keeping in mind -our [code of conduct] and [contribution guidelines]. - -[code of conduct]: ../../CODE_OF_CONDUCT.md - -[contribution guidelines]: ../../CONTRIBUTING.md - -## Security - -Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs deleted file mode 100644 index 265790f140..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/src/context.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Unit-testing context for Stylus contracts. -use std::sync::{Mutex, MutexGuard}; - -use stylus_sdk::{alloy_primitives::uint, prelude::StorageType}; - -use crate::storage::reset_storage; - -/// A global static mutex. -/// -/// We use this for scenarios where concurrent mutation of storage is wanted. -/// For example, when a test harness is running, this ensures each test -/// accesses storage in an non-overlapping manner. -/// -/// See [`with_context`]. -pub(crate) static STORAGE_MUTEX: Mutex<()> = Mutex::new(()); - -/// Acquires access to storage. -pub fn acquire_storage() -> MutexGuard<'static, ()> { - STORAGE_MUTEX.lock().unwrap_or_else(|e| { - reset_storage(); - e.into_inner() - }) -} - -/// Decorates a closure by running it with exclusive access to storage. -#[allow(clippy::module_name_repetitions)] -pub fn with_context(closure: impl FnOnce(&mut C)) { - let _lock = acquire_storage(); - let mut contract = C::default(); - closure(&mut contract); - reset_storage(); -} - -/// Initializes fields of contract storage and child contract storages with -/// default values. -pub trait DefaultStorage: StorageType { - /// Initializes fields of contract storage and child contract storages with - /// default values. - #[must_use] - fn default() -> Self { - unsafe { Self::new(uint!(0_U256), 0) } - } -} - -impl DefaultStorage for ST {} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs deleted file mode 100644 index 98fcd5dbd0..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! # Motsu - Unit Testing for Stylus -//! -//! This crate enables unit-testing for Stylus contracts. It abstracts away the -//! machinery necessary for writing tests behind a -//! [`#[motsu::test]`][test_attribute] procedural macro. -//! -//! The name `motsu` is an analogy to the place where you put your fingers to -//! hold a stylus pen. -//! -//! ## Usage -//! -//! Annotate tests with [`#[motsu::test]`][test_attribute] instead of `#[test]` -//! to get access to VM affordances. -//! -//! Note that we require contracts to implement -//! `stylus_sdk::prelude::StorageType`. This trait is typically implemented by -//! default with `stylus_proc::sol_storage` macro. -//! -//! ```rust -//! #[cfg(test)] -//! mod tests { -//! use contracts::token::erc20::Erc20; -//! -//! #[motsu::test] -//! fn reads_balance(contract: Erc20) { -//! let balance = contract.balance_of(Address::ZERO); // Access storage. -//! assert_eq!(balance, U256::ZERO); -//! } -//! } -//! ``` -//! -//! Annotating a test function that accepts no parameters will make -//! [`#[motsu::test]`][test_attribute] behave the same as `#[test]`. -//! -//! ```rust,ignore -//! #[cfg(test)] -//! mod tests { -//! #[motsu::test] // Equivalent to #[test] -//! fn test_fn() { -//! ... -//! } -//! } -//! ``` -//! -//! Note that currently, test suites using [`motsu::test`][test_attribute] will -//! run serially because of global access to storage. -//! -//! ### Notice -//! -//! We maintain this crate on a best-effort basis. We use it extensively on our -//! own tests, so we will add here any symbols we may need. However, since we -//! expect this to be a temporary solution, don't expect us to address all -//! requests. -//! -//! [test_attribute]: crate::test -mod context; -pub mod prelude; -mod shims; -mod storage; - -pub use motsu_proc::test; diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs deleted file mode 100644 index b4b541a8f8..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/src/prelude.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Common imports for `motsu` tests. -pub use crate::{ - context::{acquire_storage, with_context, DefaultStorage}, - shims::*, - storage::reset_storage, -}; diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs deleted file mode 100644 index 0860a61423..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/src/shims.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! Shims that mock common host imports in Stylus `wasm` programs. -//! -//! Most of the documentation is taken from the [Stylus source]. -//! -//! We allow unsafe here because safety is guaranteed by the Stylus team. -//! -//! [Stylus source]: https://github.com/OffchainLabs/stylus/blob/484efac4f56fb70f96d4890748b8ec2543d88acd/arbitrator/wasm-libraries/user-host-trait/src/lib.rs -//! -//! ## Motivation -//! -//! Without these shims we can't currently run unit tests for stylus contracts, -//! since the symbols the compiled binaries expect to find are not there. -//! -//! If you run `cargo test` on a fresh Stylus project, it will error with: -//! -//! ```terminal -//! dyld[97792]: missing symbol called -//! ``` -//! -//! This crate is a temporary solution until the Stylus team provides us with a -//! different and more stable mechanism for unit-testing our contracts. -//! -//! ## Usage -//! -//! Import these shims in your test modules as `motsu::prelude::*` to populate -//! the namespace with the appropriate symbols. -//! -//! ```rust,ignore -//! #[cfg(test)] -//! mod tests { -//! use contracts::token::erc20::Erc20; -//! -//! #[motsu::test] -//! fn reads_balance(contract: Erc20) { -//! let balance = contract.balance_of(Address::ZERO); // Access storage. -//! assert_eq!(balance, U256::ZERO); -//! } -//! } -//! ``` -//! -//! Note that for proper usage, tests should have exclusive access to storage, -//! since they run in parallel, which may cause undesired results. -//! -//! One solution is to wrap tests with a function that acquires a global mutex: -//! -//! ```rust,no_run -//! use std::sync::{Mutex, MutexGuard}; -//! -//! use motsu::prelude::reset_storage; -//! -//! pub static STORAGE_MUTEX: Mutex<()> = Mutex::new(()); -//! -//! pub fn acquire_storage() -> MutexGuard<'static, ()> { -//! STORAGE_MUTEX.lock().unwrap() -//! } -//! -//! pub fn with_context(closure: impl FnOnce(&mut C)) { -//! let _lock = acquire_storage(); -//! let mut contract = C::default(); -//! closure(&mut contract); -//! reset_storage(); -//! } -//! -//! #[motsu::test] -//! fn reads_balance() { -//! let balance = token.balance_of(Address::ZERO); -//! assert_eq!(balance, U256::ZERO); -//! } -//! ``` -#![allow(clippy::missing_safety_doc)] -use std::slice; - -use tiny_keccak::{Hasher, Keccak}; - -use crate::storage::{read_bytes32, write_bytes32, STORAGE}; - -pub(crate) const WORD_BYTES: usize = 32; -pub(crate) type Bytes32 = [u8; WORD_BYTES]; - -/// Efficiently computes the [`keccak256`] hash of the given preimage. -/// The semantics are equivalent to that of the EVM's [`SHA3`] opcode. -/// -/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 -/// [`SHA3`]: https://www.evm.codes/#20 -#[no_mangle] -pub unsafe extern "C" fn native_keccak256( - bytes: *const u8, - len: usize, - output: *mut u8, -) { - let mut hasher = Keccak::v256(); - - let data = unsafe { slice::from_raw_parts(bytes, len) }; - hasher.update(data); - - let output = unsafe { slice::from_raw_parts_mut(output, WORD_BYTES) }; - hasher.finalize(output); -} - -/// Reads a 32-byte value from permanent storage. Stylus's storage format is -/// identical to that of the EVM. This means that, under the hood, this hostio -/// is accessing the 32-byte value stored in the EVM state trie at offset -/// `key`, which will be `0` when not previously set. The semantics, then, are -/// equivalent to that of the EVM's [`SLOAD`] opcode. -/// -/// [`SLOAD`]: https://www.evm.codes/#54 -/// -/// # Panics -/// -/// May panic if unable to lock `STORAGE`. -#[no_mangle] -pub unsafe extern "C" fn storage_load_bytes32(key: *const u8, out: *mut u8) { - let key = unsafe { read_bytes32(key) }; - - let value = STORAGE - .lock() - .unwrap() - .get(&key) - .map(Bytes32::to_owned) - .unwrap_or_default(); - - unsafe { write_bytes32(out, value) }; -} - -/// Writes a 32-byte value to the permanent storage cache. Stylus's storage -/// format is identical to that of the EVM. This means that, under the hood, -/// this hostio represents storing a 32-byte value into the EVM state trie at -/// offset `key`. Refunds are tabulated exactly as in the EVM. The semantics, -/// then, are equivalent to that of the EVM's [`SSTORE`] opcode. -/// -/// Note: because the value is cached, one must call `storage_flush_cache` to -/// persist it. -/// -/// [`SSTORE`]: https://www.evm.codes/#55 -/// -/// # Panics -/// -/// May panic if unable to lock `STORAGE`. -#[no_mangle] -pub unsafe extern "C" fn storage_cache_bytes32( - key: *const u8, - value: *const u8, -) { - let (key, value) = unsafe { (read_bytes32(key), read_bytes32(value)) }; - STORAGE.lock().unwrap().insert(key, value); -} - -/// Persists any dirty values in the storage cache to the EVM state trie, -/// dropping the cache entirely if requested. Analogous to repeated invocations -/// of [`SSTORE`]. -/// -/// [`SSTORE`]: https://www.evm.codes/#55 -pub fn storage_flush_cache(_: bool) { - // No-op: we don't use the cache in our unit-tests. -} - -/// Dummy msg sender set for tests. -pub const MSG_SENDER: &[u8; 42] = b"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"; - -/// Dummy contract address set for tests. -pub const CONTRACT_ADDRESS: &[u8; 42] = - b"0xdCE82b5f92C98F27F116F70491a487EFFDb6a2a9"; - -/// Arbitrum's CHAID ID. -pub const CHAIN_ID: u64 = 42161; - -/// Externally Owned Account (EOA) code hash. -pub const EOA_CODEHASH: &[u8; 66] = - b"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; - -/// Gets the address of the account that called the program. For normal -/// L2-to-L2 transactions the semantics are equivalent to that of the EVM's -/// [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. -/// -/// For L1-to-L2 retryable ticket transactions, the top-level sender's address -/// will be aliased. See [`Retryable Ticket Address Aliasing`][aliasing] for -/// more information on how this works. -/// -/// [`CALLER`]: https://www.evm.codes/#33 -/// [`DELEGATE_CALL`]: https://www.evm.codes/#f4 -/// [aliasing]: https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing -/// -/// # Panics -/// -/// May panic if fails to parse `MSG_SENDER` as an address. -#[no_mangle] -pub unsafe extern "C" fn msg_sender(sender: *mut u8) { - let addr = const_hex::const_decode_to_array::<20>(MSG_SENDER).unwrap(); - std::ptr::copy(addr.as_ptr(), sender, 20); -} - -/// Gets the address of the current program. The semantics are equivalent to -/// that of the EVM's [`ADDRESS`] opcode. -/// -/// [`ADDRESS`]: https://www.evm.codes/#30 -/// -/// # Panics -/// -/// May panic if fails to parse `CONTRACT_ADDRESS` as an address. -#[no_mangle] -pub unsafe extern "C" fn contract_address(address: *mut u8) { - let addr = - const_hex::const_decode_to_array::<20>(CONTRACT_ADDRESS).unwrap(); - std::ptr::copy(addr.as_ptr(), address, 20); -} - -/// Gets the chain ID of the current chain. The semantics are equivalent to -/// that of the EVM's [`CHAINID`] opcode. -/// -/// [`CHAINID`]: https://www.evm.codes/#46 -#[no_mangle] -pub unsafe extern "C" fn chainid() -> u64 { - CHAIN_ID -} - -/// Emits an EVM log with the given number of topics and data, the first bytes -/// of which should be the 32-byte-aligned topic data. The semantics are -/// equivalent to that of the EVM's [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and -/// [`LOG4`] opcodes based on the number of topics specified. Requesting more -/// than `4` topics will induce a revert. -/// -/// [`LOG0`]: https://www.evm.codes/#a0 -/// [`LOG1`]: https://www.evm.codes/#a1 -/// [`LOG2`]: https://www.evm.codes/#a2 -/// [`LOG3`]: https://www.evm.codes/#a3 -/// [`LOG4`]: https://www.evm.codes/#a4 -#[no_mangle] -pub unsafe extern "C" fn emit_log(_: *const u8, _: usize, _: usize) { - // No-op: we don't check for events in our unit-tests. -} - -/// Gets the code hash of the account at the given address. -/// The semantics are equivalent to that of the EVM's [`EXT_CODEHASH`] opcode. -/// Note that the code hash of an account without code will be the empty hash -/// `keccak("") = -/// c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. -/// -/// [`EXT_CODEHASH`]: https://www.evm.codes/#3F -/// -/// # Panics -/// -/// May panic if fails to parse `ACCOUNT_CODEHASH` as a keccack hash. -#[no_mangle] -pub unsafe extern "C" fn account_codehash(_address: *const u8, dest: *mut u8) { - let account_codehash = - const_hex::const_decode_to_array::<32>(EOA_CODEHASH).unwrap(); - - std::ptr::copy(account_codehash.as_ptr(), dest, 32); -} - -/// Returns the length of the last EVM call or deployment return result, or `0` -/// if neither have happened during the program's execution. The semantics are -/// equivalent to that of the EVM's [`RETURN_DATA_SIZE`] opcode. -/// -/// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d -#[no_mangle] -pub unsafe extern "C" fn return_data_size() -> usize { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 -} - -/// Copies the bytes of the last EVM call or deployment return result. Does not -/// revert if out of bounds, but rather copies the overlapping portion. The -/// semantics are otherwise equivalent to that of the EVM's [`RETURN_DATA_COPY`] -/// opcode. -/// -/// Returns the number of bytes written. -/// -/// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e -#[no_mangle] -pub unsafe extern "C" fn read_return_data( - _dest: *mut u8, - _offset: usize, - _size: usize, -) -> usize { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 -} - -/// Calls the contract at the given address with options for passing value and -/// to limit the amount of gas supplied. The return status indicates whether the -/// call succeeded, and is nonzero on failure. -/// -/// In both cases `return_data_len` will store the length of the result, the -/// bytes of which can be read via the `read_return_data` hostio. The bytes are -/// not returned directly so that the programmer can potentially save gas by -/// choosing which subset of the return result they'd like to copy. -/// -/// The semantics are equivalent to that of the EVM's [`CALL`] opcode, including -/// callvalue stipends and the 63/64 gas rule. This means that supplying the -/// `u64::MAX` gas can be used to send as much as possible. -/// -/// [`CALL`]: https://www.evm.codes/#f1 -#[no_mangle] -pub unsafe extern "C" fn call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, - _value: *const u8, - _gas: u64, - _return_data_len: *mut usize, -) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 -} - -/// Static calls the contract at the given address, with the option to limit the -/// amount of gas supplied. The return status indicates whether the call -/// succeeded, and is nonzero on failure. -/// -/// In both cases `return_data_len` will store the length of the result, the -/// bytes of which can be read via the `read_return_data` hostio. The bytes are -/// not returned directly so that the programmer can potentially save gas by -/// choosing which subset of the return result they'd like to copy. -/// -/// The semantics are equivalent to that of the EVM's [`STATIC_CALL`] opcode, -/// including the 63/64 gas rule. This means that supplying `u64::MAX` gas can -/// be used to send as much as possible. -/// -/// [`STATIC_CALL`]: https://www.evm.codes/#FA -#[no_mangle] -pub unsafe extern "C" fn static_call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, - _gas: u64, - _return_data_len: *mut usize, -) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 -} - -/// Delegate calls the contract at the given address, with the option to limit -/// the amount of gas supplied. The return status indicates whether the call -/// succeeded, and is nonzero on failure. -/// -/// In both cases `return_data_len` will store the length of the result, the -/// bytes of which can be read via the `read_return_data` hostio. The bytes are -/// not returned directly so that the programmer can potentially save gas by -/// choosing which subset of the return result they'd like to copy. -/// -/// The semantics are equivalent to that of the EVM's [`DELEGATE_CALL`] opcode, -/// including the 63/64 gas rule. This means that supplying `u64::MAX` gas can -/// be used to send as much as possible. -/// -/// [`DELEGATE_CALL`]: https://www.evm.codes/#F4 -#[no_mangle] -pub unsafe extern "C" fn delegate_call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, - _gas: u64, - _return_data_len: *mut usize, -) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 -} - -/// Gets a bounded estimate of the Unix timestamp at which the Sequencer -/// sequenced the transaction. See [`Block Numbers and Time`] for more -/// information on how this value is determined. -/// -/// [`Block Numbers and Time`]: https://developer.arbitrum.io/time -#[no_mangle] -pub unsafe extern "C" fn block_timestamp() -> u64 { - // Epoch timestamp: 1st January 2025 00::00::00 - 1_735_689_600 -} diff --git a/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs b/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs deleted file mode 100644 index 4e8d23d4aa..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/motsu/src/storage.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Shims for storage operations. -use std::{collections::HashMap, ptr, sync::Mutex}; - -use once_cell::sync::Lazy; - -use crate::shims::{Bytes32, WORD_BYTES}; - -/// Storage mock: A global mutable key-value store. -pub(crate) static STORAGE: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -/// Read the word at address `key`. -pub(crate) unsafe fn read_bytes32(key: *const u8) -> Bytes32 { - let mut res = Bytes32::default(); - ptr::copy(key, res.as_mut_ptr(), WORD_BYTES); - res -} - -/// Write the word `val` to the location pointed by `key`. -pub(crate) unsafe fn write_bytes32(key: *mut u8, val: Bytes32) { - ptr::copy(val.as_ptr(), key, WORD_BYTES); -} - -/// Clears storage, removing all key-value pairs. -/// -/// # Panics -/// -/// May panic if the storage lock is already held by the current thread. -#[allow(clippy::module_name_repetitions)] -pub fn reset_storage() { - STORAGE.lock().unwrap().clear(); -} From 1397b2b5bb677c8affec5685564bf29d79753e12 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 11 Nov 2024 16:37:46 +0000 Subject: [PATCH 113/160] chore: changed Fixedbytes<32> to B256 --- .../stylus/contracts/src/pyth/functions.rs | 18 ++++----- .../sdk/stylus/contracts/src/pyth/mock.rs | 39 ++++++++----------- .../contracts/src/pyth/pyth_contract.rs | 30 +++++++------- .../sdk/stylus/contracts/src/pyth/types.rs | 16 +++++--- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index b17582d4ae..91aacca50d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -15,7 +15,7 @@ use crate::pyth::types::{ use crate::utils::helpers::{call_helper, delegate_call_helper}; use alloc::vec::Vec; use stylus_sdk::storage::TopLevelStorage; -use alloy_primitives::{ Address, FixedBytes, U256, Bytes}; +use alloy_primitives::{ Address, B256, U256, Bytes}; use crate::pyth::mock::DecodeDataType; use alloy_sol_types::SolType; @@ -32,7 +32,7 @@ use alloy_sol_types::SolType; pub fn get_price_no_older_than( storage: &mut impl TopLevelStorage, pyth_address: Address, - id: FixedBytes<32>, + id: B256, age: U256, ) -> Result> { let price_call = call_helper::(storage, pyth_address, (id, age,))?; @@ -69,7 +69,7 @@ pub fn get_update_fee( pub fn get_ema_price_unsafe( storage: &mut impl TopLevelStorage, pyth_address: Address, - id: FixedBytes<32>, + id: B256, ) -> Result> { let ema_price = call_helper::(storage, pyth_address, (id,))?; Ok(ema_price.price) @@ -88,7 +88,7 @@ pub fn get_ema_price_unsafe( pub fn get_ema_price_no_older_than( storage: &mut impl TopLevelStorage, pyth_address: Address, - id: FixedBytes<32>, + id: B256, age: U256, ) -> Result> { let ema_price = call_helper::(storage, pyth_address, (id, age,))?; @@ -107,7 +107,7 @@ pub fn get_ema_price_no_older_than( pub fn get_price_unsafe( storage: &mut impl TopLevelStorage, pyth_address: Address, - id: FixedBytes<32>, + id: B256, ) -> Result> { let price = call_helper::(storage, pyth_address, (id,))?; let price = Price { @@ -168,7 +168,7 @@ pub fn update_price_feeds_if_necessary( storage: &mut impl TopLevelStorage, pyth_address: Address, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, publish_times: Vec, ) -> Result<(), Vec> { delegate_call_helper::( @@ -195,7 +195,7 @@ pub fn parse_price_feed_updates( storage: &mut impl TopLevelStorage, pyth_address: Address, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64, ) -> Result, Vec> { @@ -218,7 +218,7 @@ pub fn parse_price_feed_updates_unique( storage: &mut impl TopLevelStorage, pyth_address: Address, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64 ) -> Result, Vec> { @@ -243,7 +243,7 @@ pub fn parse_price_feed_updates_unique( /// # Returns /// - `Vec`: A byte vector containing the encoded update data for the price feed. pub fn create_price_feed_update_data( - id: FixedBytes<32>, + id: B256, price: i64, conf: u64, expo: i32, diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 5286ad0d4c..aa7f7da738 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -1,5 +1,5 @@ use alloc::vec::Vec; -use alloy_primitives::{Uint, FixedBytes, U256, Bytes}; +use alloy_primitives::{Uint, U256, Bytes, B256}; use alloy_sol_types::{ sol_data::Uint as SolUInt, SolType, SolValue}; use stylus_sdk::{abi::Bytes as AbiBytes,evm, msg, prelude::*}; use crate::{pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE}; @@ -32,15 +32,15 @@ impl MockPythContract { } - fn query_price_feed(&self, id: FixedBytes<32>) -> Result, Vec> { + fn query_price_feed(&self, id: B256) -> Result, Vec> { let price_feed = self.price_feeds.get(id).to_price_feed(); - if price_feed.id.eq(&FixedBytes::<32>::ZERO) { + if price_feed.id.eq(&B256::ZERO) { return Err(Error::PriceFeedNotFound(PriceFeedNotFound {}).into()); } Ok(price_feed.abi_encode()) } - fn price_feed_exists(&self, id:FixedBytes<32>) -> bool { + fn price_feed_exists(&self, id:B256) -> bool { self.price_feeds.getter(id).id.is_empty() } @@ -97,7 +97,7 @@ impl MockPythContract { fn parse_price_feed_updates( &mut self, update_data:Vec, - price_ids:Vec>, + price_ids:Vec, min_publish_time:u64, max_publish_time:u64 ) -> Result, Vec>{ @@ -108,7 +108,7 @@ impl MockPythContract { fn parse_price_feed_updates_unique( &mut self, update_data:Vec, - price_ids:Vec>, + price_ids:Vec, min_publish_time:u64, max_publish_time:u64 ) -> Result, Vec>{ @@ -116,7 +116,7 @@ impl MockPythContract { } fn create_price_feed_update_data(&self, - id:FixedBytes<32>, + id:B256, price:i64, conf:u64, expo:i32, @@ -141,7 +141,7 @@ impl MockPythContract { fn parse_price_feed_updates_internal(&mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time:u64, max_publish_time:u64, unique:bool @@ -183,7 +183,7 @@ impl MockPythContract { { break; } else { - feeds[i].id = FixedBytes::<32>::ZERO; + feeds[i].id = B256::ZERO; } } @@ -197,10 +197,10 @@ impl MockPythContract { } } -pub fn create_price_feed_update_data_list() -> (Vec, Vec>) { +pub fn create_price_feed_update_data_list() -> (Vec, Vec) { let id = ["ETH","SOL","BTC"].map(|x| { let x = keccak_const::Keccak256::new().update(x.as_bytes()).finalize().to_vec(); - return FixedBytes::<32>::from_slice(&x) ; + return B256::from_slice(&x); }); let mut price_feed_data_list = Vec::new(); for i in 0..3 { @@ -214,7 +214,7 @@ pub fn create_price_feed_update_data_list() -> (Vec, Vec>) #[cfg(all(test, feature = "std"))] mod tests { use alloc::vec; - use alloy_primitives::{ U256, FixedBytes}; + use alloy_primitives::{ U256, B256}; use stylus_sdk::abi::Bytes; use crate::pyth::mock::{MockPythContract, DecodeDataType}; @@ -228,8 +228,8 @@ mod tests { const EMA_CONF: u64 = 1000; const PREV_PUBLISH_TIME: u64 = 1000; - fn generate_bytes() -> FixedBytes<32> { - FixedBytes::<32>::repeat_byte(30) + fn generate_bytes() -> B256 { + B256::repeat_byte(30) } #[motsu::test] @@ -241,7 +241,8 @@ mod tests { #[motsu::test] fn error_initialize_mock_contract(contract: MockPythContract) { - let err = contract.initialize(U256::from(0), U256::from(0)).expect_err("should not initialize with invalid parameters"); + let err = contract.initialize(U256::from(0), U256::from(0)) ; + assert!(err.is_err()) } #[motsu::test] @@ -298,12 +299,4 @@ mod tests { assert_eq!(valid_time_period, U256::from(1000)); } - #[motsu::test] - fn can_update_price_feeds(contract: MockPythContract) { - let _ = contract.initialize(U256::from(1000), U256::from(1000)); - let id = generate_bytes(); - //let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, U256::from(1000), PREV_PUBLISH_TIME); - //let mut update_data: Vec = vec![]; - //update_data.push(Bytes::from(price_feed_created)); - } } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index 6757f75524..b3728860c6 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -1,7 +1,7 @@ use alloy_sol_types::SolValue; use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; use alloc::vec::Vec; -use alloy_primitives::{ FixedBytes, U256 , Bytes }; +use alloy_primitives::{ U256 , Bytes, B256 }; use crate::pyth::functions::{ get_price_unsafe, get_price_no_older_than, @@ -27,7 +27,7 @@ pub trait IPyth { /// /// # Returns /// - `Result, Self::Error>`: The price data in bytes, or an error. - fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + fn get_price_unsafe(&mut self, id: B256) -> Result, Self::Error>; /// Retrieves a price that is no older than a specified `age`. /// @@ -37,7 +37,7 @@ pub trait IPyth { /// /// # Returns /// - `Result, Self::Error>`: The price data in bytes, or an error. - fn get_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error>; + fn get_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error>; /// Retrieves the exponentially-weighted moving average (EMA) price without recency checks. /// @@ -46,7 +46,7 @@ pub trait IPyth { /// /// # Returns /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. - fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error>; + fn get_ema_price_unsafe(&mut self, id: B256) -> Result, Self::Error>; /// Retrieves an EMA price that is no older than the specified `age`. /// @@ -56,7 +56,7 @@ pub trait IPyth { /// /// # Returns /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error>; + fn get_ema_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error>; /// Updates price feeds with the given data. /// @@ -79,7 +79,7 @@ pub trait IPyth { fn update_price_feeds_if_necessary( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, publish_times: Vec, ) -> Result<(), Self::Error>; @@ -114,7 +114,7 @@ pub trait IPyth { fn parse_price_feed_updates( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error>; @@ -132,7 +132,7 @@ pub trait IPyth { fn parse_price_feed_updates_unique( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error>; @@ -154,25 +154,25 @@ impl IPyth for PythContract { type Error = Vec; - fn get_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + fn get_price_unsafe(&mut self, id: B256) -> Result, Self::Error> { let price = get_price_unsafe(self, self._ipyth.get(), id)?; let data = price.abi_encode(); Ok(data) } - fn get_price_no_older_than(&mut self,id: FixedBytes<32>, age: U256) -> Result, Self::Error> { + fn get_price_no_older_than(&mut self,id: B256, age: U256) -> Result, Self::Error> { let price = get_price_no_older_than(self, self._ipyth.get(), id,age)?; let data = price.abi_encode(); Ok(data) } - fn get_ema_price_unsafe(&mut self, id: FixedBytes<32>) -> Result, Self::Error> { + fn get_ema_price_unsafe(&mut self, id: B256) -> Result, Self::Error> { let price = get_ema_price_unsafe(self, self._ipyth.get(), id)?; let data = price.abi_encode(); Ok(data) } - fn get_ema_price_no_older_than(&mut self, id: FixedBytes<32>, age: U256) -> Result, Self::Error> { + fn get_ema_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error> { let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,age)?; let data = price.abi_encode(); Ok(data) @@ -200,7 +200,7 @@ impl IPyth for PythContract { fn update_price_feeds_if_necessary( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, publish_times: Vec, ) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); @@ -212,7 +212,7 @@ impl IPyth for PythContract { fn parse_price_feed_updates( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error> { @@ -225,7 +225,7 @@ impl IPyth for PythContract { fn parse_price_feed_updates_unique( &mut self, update_data: Vec, - price_ids: Vec>, + price_ids: Vec, min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error> { diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 4b620129df..8eeccc7b6d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -214,18 +214,22 @@ impl StoragePriceFeed { #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::{ FixedBytes, U256}; + use alloy_primitives::{ B256, U256}; use crate::pyth::types::{PriceFeed, Price, StoragePriceFeed, StoragePrice}; // Updated constants to use uppercase naming convention const PRICE: i64 = 1000; const CONF: u64 = 1000; - const EXPO: i32 = 1000; - const EMA_PRICE: i64 = 1000; - const EMA_CONF: u64 = 1000; + const EXPO: i32 = 1000; - fn generate_bytes() -> FixedBytes<32> { - FixedBytes::<32>::repeat_byte(30) + + + /// Generates a 256-bit hash filled with the byte value 30. + /// + /// # Returns + /// - `B256`: A 256-bit hash where each byte is set to 30. + fn generate_bytes() -> B256 { + B256::repeat_byte(30) } #[motsu::test] From 6b37ea56de408c28e7c54e045a303a8ce22fb091 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 11 Nov 2024 18:00:45 +0000 Subject: [PATCH 114/160] chore: removed local e2e testing library --- target_chains/ethereum/sdk/stylus/Cargo.lock | 2 + target_chains/ethereum/sdk/stylus/Cargo.toml | 7 +- .../sdk/stylus/lib/e2e-proc/Cargo.toml | 21 --- .../sdk/stylus/lib/e2e-proc/README.md | 5 - .../sdk/stylus/lib/e2e-proc/src/lib.rs | 21 --- .../sdk/stylus/lib/e2e-proc/src/test.rs | 47 ------ .../ethereum/sdk/stylus/lib/e2e/Cargo.toml | 23 --- .../ethereum/sdk/stylus/lib/e2e/README.md | 148 ------------------ .../sdk/stylus/lib/e2e/src/account.rs | 121 -------------- .../ethereum/sdk/stylus/lib/e2e/src/deploy.rs | 72 --------- .../sdk/stylus/lib/e2e/src/environment.rs | 37 ----- .../ethereum/sdk/stylus/lib/e2e/src/error.rs | 116 -------------- .../ethereum/sdk/stylus/lib/e2e/src/event.rs | 23 --- .../ethereum/sdk/stylus/lib/e2e/src/lib.rs | 99 ------------ .../sdk/stylus/lib/e2e/src/project.rs | 105 ------------- .../sdk/stylus/lib/e2e/src/receipt.rs | 17 -- .../ethereum/sdk/stylus/lib/e2e/src/system.rs | 85 ---------- 17 files changed, 3 insertions(+), 946 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs delete mode 100644 target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs diff --git a/target_chains/ethereum/sdk/stylus/Cargo.lock b/target_chains/ethereum/sdk/stylus/Cargo.lock index 950aec6da4..a9d7082a4c 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.lock +++ b/target_chains/ethereum/sdk/stylus/Cargo.lock @@ -1357,6 +1357,7 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "e2e" version = "0.1.0" +source = "git+https://github.com/Ifechukwudaniel/e2e-stylus#bbcc5885f0b45a53d1f87adcbf31c0b5e7e144d2" dependencies = [ "alloy", "e2e-proc", @@ -1371,6 +1372,7 @@ dependencies = [ [[package]] name = "e2e-proc" version = "0.1.0" +source = "git+https://github.com/Ifechukwudaniel/e2e-proc-stylus#6894e8b62896ed16fb3128664ec2f390536c27c5" dependencies = [ "proc-macro2", "quote", diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 3219a43488..38c595f336 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -1,15 +1,12 @@ [workspace] members = [ "contracts", - "lib/e2e", - "lib/e2e-proc", "examples/function-calls", "examples/proxy-calls", "benches" ] default-members = [ "contracts", - "lib/e2e-proc", "examples/function-calls", "examples/proxy-calls", ] @@ -78,9 +75,7 @@ quote = "1.0.35" # members motsu = "0.1.0" -motsu-proc = "0.1.0" -e2e = { path = "lib/e2e" } -e2e-proc = {path = "lib/e2e-proc"} +e2e = { git = "https://github.com/Ifechukwudaniel/e2e-stylus"} pyth-stylus ={path ="contracts"} [profile.release] diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml deleted file mode 100644 index aaa76ad183..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "e2e-proc" -description = "End-to-end Testing Procedural Macros" -version = "0.1.0" -categories = ["development-tools::testing", "cryptography::cryptocurrencies"] -keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -proc-macro2.workspace = true -quote.workspace = true -syn.workspace = true - -[lib] -proc-macro = true - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md deleted file mode 100644 index c9ebfaecfd..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# End-to-end Procedural Macros - -This crate contains procedural macros used in [e2e]. - -[e2e]: ../e2e/README.md diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs deleted file mode 100644 index f0f7bae2f2..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![doc = include_str!("../README.md")] -use proc_macro::TokenStream; - -mod test; - -/// Defines an end-to-end Stylus contract test that sets up `e2e::Account`s -/// based on the function's parameters. -/// -/// # Examples -/// -/// ```rust,ignore -/// #[e2e::test] -/// async fn foo(alice: Account, bob: Account) -> eyre::Result<()> { -/// let charlie = Account::new().await?; -/// // ... -/// } -/// ``` -#[proc_macro_attribute] -pub fn test(attr: TokenStream, input: TokenStream) -> TokenStream { - test::test(&attr, input) -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs b/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs deleted file mode 100644 index f981d8950b..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e-proc/src/test.rs +++ /dev/null @@ -1,47 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, FnArg}; - -/// Shorthand to print nice errors. -macro_rules! error { - ($tokens:expr, $($msg:expr),+ $(,)?) => {{ - let error = syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+)); - return error.to_compile_error().into(); - }}; - (@ $tokens:expr, $($msg:expr),+ $(,)?) => {{ - return Err(syn::Error::new(syn::spanned::Spanned::span(&$tokens), format!($($msg),+))) - }}; -} - -/// Defines an end-to-end test that injects test accounts through parameters. -/// -/// For more information see [`crate::test`]. -pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { - let item_fn = parse_macro_input!(input as syn::ItemFn); - let attrs = &item_fn.attrs; - let sig = &item_fn.sig; - let fn_name = &sig.ident; - let fn_return_type = &sig.output; - let fn_stmts = &item_fn.block.stmts; - let fn_args = &sig.inputs; - - let account_declarations = fn_args.into_iter().map(|arg| { - let FnArg::Typed(arg) = arg else { - error!(arg, "unexpected receiver argument in test signature"); - }; - let account_arg_binding = &arg.pat; - let account_ty = &arg.ty; - quote! { - let #account_arg_binding = #account_ty::new().await?; - } - }); - quote! { - #( #attrs )* - #[tokio::test] - async fn #fn_name() #fn_return_type { - #( #account_declarations )* - #( #fn_stmts )* - } - } - .into() -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml b/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml deleted file mode 100644 index 84a9c6df0c..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "e2e" -description = "End-to-end Testing for Stylus" -version = "0.1.0" -categories = ["development-tools::testing", "cryptography::cryptocurrencies"] -keywords = ["arbitrum", "ethereum", "stylus", "integration-testing", "tests"] -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true - -[dependencies] -alloy.workspace = true -tokio = { workspace = true, features = ["process"] } -eyre.workspace = true -regex.workspace = true -once_cell.workspace = true -koba.workspace = true -e2e-proc.workspace = true -toml = "0.8.13" - -[lints] -workspace = true diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/README.md b/target_chains/ethereum/sdk/stylus/lib/e2e/README.md deleted file mode 100644 index dbd5c850f1..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/README.md +++ /dev/null @@ -1,148 +0,0 @@ -# End-to-end Testing for Stylus Contracts - -This end-to-end testing crate provides affordances to test your contracts in a -blockchain environment. - -This crate is currently coupled to [`nitro-testnode`] and [`koba`]. - -[`nitro-testnode`]: https://github.com/OffchainLabs/nitro-testnode - -[`koba`]: https://github.com/OpenZeppelin/koba - -## Usage - -Refer to our end-to-end tests [GitHub workflow] for a working example of a full -tests suite using the `e2e` crate. - -[GitHub workflow]: ../../.github/workflows/e2e-tests.yml - -### Accounts - -Decorate your tests with the `test` procedural macro: a thin wrapper over -`tokio::test` that sets up `Account`s for your test. - -```rust,ignore -#[e2e::test] -async fn accounts_are_funded(alice: Account) -> eyre::Result<()> { - let balance = alice.wallet.get_balance(alice.address()).await?; - let expected = parse_ether("10")?; - assert_eq!(expected, balance); - Ok(()) -} -``` - -A `Account` is a thin wrapper over a [`PrivateKeySigner`] and an `alloy` provider with a -[`WalletFiller`]. Both of them are connected to the RPC endpoint defined by the -`RPC_URL` environment variable. This means that a `Account` is the main proxy -between the RPC and the test code. - -All accounts start with 10 ETH as balance. You can have multiple accounts as -parameters of your test function, or you can create new accounts separately: - -```rust,ignore -#[e2e::test] -async fn foo(alice: Account, bob: Account) -> eyre::Result<()> { - let charlie = Account::new().await?; - // ... -} -``` - -[`LocalWallet`]: https://github.com/alloy-rs/alloy/blob/8aa54828c025a99bbe7e2d4fc9768605d172cc6d/crates/signer-local/src/lib.rs#L37 - -[`WalletFiller`]: https://github.com/alloy-rs/alloy/blob/8aa54828c025a99bbe7e2d4fc9768605d172cc6d/crates/provider/src/fillers/wallet.rs#L30 - -### Contracts - -We use `koba` to deploy contracts to the blockchain. This is not required, a -separate mechanism for deployment can be used. `Deployer` type exposes `Deployer::deploy` -method that abstracts away the mechanism used in our workflow. - -Given a Solidity contract with a constructor at path `src/constructor.sol` like -this: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; - -contract Example { - mapping(address account => uint256) private _balances; - mapping(address account => mapping(address spender => uint256)) - private _allowances; - uint256 private _totalSupply; - string private _name; - string private _symbol; - - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } -} -``` - -Account type exposes `Account::as_deployer` method that returns `Deployer` type. -It will facilitate deployment of the contract marked with the `#[entrypoint]` macro. -Then you can configure deployment with default constructor: - -```rust,ignore -let contract_addr = alice.as_deployer().deploy().await?.address()?; -``` - -Or with a custom constructor. -Note that the abi-encodable `Example::constructorCall` should be generated -with `sol!("src/constructor.sol")` macro. - -```rust,ignore -let ctr = Example::constructorCall { - name_: "Token".to_owned(), - symbol_: "TKN".to_owned(), -}; -let receipt = alice - .as_deployer() - .with_constructor(ctr) - .deploy() - .await?; -``` - -Then altogether, your first test case can look like this: - -```rust,ignore -sol!("src/constructor.sol") - -#[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - let ctr = Example::constructorCall { - name_: "Token".to_owned(), - symbol_: "TKN".to_owned(), - }; - let contract_addr = alice - .as_deployer() - .with_constructor(ctr) - .deploy() - .await? - .address()?; - let contract = Erc20::new(contract_addr, &alice.wallet); - - let Erc20::nameReturn { name } = contract.name().call().await?; - let Erc20::symbolReturn { symbol } = contract.symbol().call().await?; - - assert_eq!(name, TOKEN_NAME.to_owned()); - assert_eq!(symbol, TOKEN_SYMBOL.to_owned()); - Ok(()) -} -``` - -## Notice - -We maintain this crate on a best-effort basis. We use it extensively on our own -tests, so we will continue to add more affordances as we need them. - -That being said, please do open an issue to start a discussion, keeping in mind -our [code of conduct] and [contribution guidelines]. - -[code of conduct]: ../../CODE_OF_CONDUCT.md - -[contribution guidelines]: ../../CONTRIBUTING.md - -## Security - -Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs deleted file mode 100644 index 83d4cd2630..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/account.rs +++ /dev/null @@ -1,121 +0,0 @@ -use alloy::{ - network::EthereumWallet, - primitives::{Address, B256}, - providers::{Provider, ProviderBuilder}, - signers::{local::PrivateKeySigner, Signature, Signer}, -}; -use eyre::Result; -use once_cell::sync::Lazy; -use tokio::sync::{Mutex, MutexGuard}; - -use crate::{ - deploy::Deployer, - system::{fund_account, Wallet, RPC_URL_ENV_VAR_NAME}, -}; - -const DEFAULT_FUNDING_ETH: u32 = 100; - -/// Type that corresponds to a test account. -#[derive(Clone, Debug)] -pub struct Account { - /// The account's local private key wrapper. - pub signer: PrivateKeySigner, - /// The account's wallet -- an `alloy` provider with a `WalletFiller`. - pub wallet: Wallet, -} - -impl Account { - /// Create a new account with a default funding of [`DEFAULT_FUNDING_ETH`]. - /// - /// # Errors - /// - /// May fail if funding the newly created account fails. - pub async fn new() -> Result { - AccountFactory::create().await - } - - /// Get a hex-encoded String representing this account's private key. - #[must_use] - pub fn pk(&self) -> String { - alloy::hex::encode(self.signer.to_bytes()) - } - - /// Retrieve this account's address. - #[must_use] - pub fn address(&self) -> Address { - self.signer.address() - } - - /// The rpc endpoint this account's provider is connect to. - #[must_use] - pub fn url(&self) -> &str { - self.wallet.client().transport().url() - } - - /// Sign the given hash. - /// - /// # Panics - /// - /// May fail when the method is not implemented for `Signer`. Should not - /// happen. - pub async fn sign_hash(&self, hash: &B256) -> Signature { - self.signer.sign_hash(hash).await.expect("should sign a hash") - } - - /// Sign the given message. - /// - /// # Panics - /// - /// May fail when the method is not implemented for `Signer`. Should not - /// happen. - pub async fn sign_message(&self, message: &[u8]) -> Signature { - self.signer.sign_message(message).await.expect("should sign a message") - } - - /// Create a configurable smart contract deployer on behalf of this account. - pub fn as_deployer(&self) -> Deployer { - Deployer::new(self.url().to_string(), self.pk()) - } -} - -/// A unit struct used as a synchronization mechanism in -/// [`SYNC_ACCOUNT_FACTORY`]. -struct AccountFactory; - -impl AccountFactory { - /// Get access to the factory in a synchronized manner. - async fn lock() -> MutexGuard<'static, Self> { - /// Since after wallet generation accounts get funded in the nitro test - /// node from a single "god" wallet, we must synchronize account - /// creation (otherwise the nonce will be too low). - static SYNC_ACCOUNT_FACTORY: Lazy> = - Lazy::new(|| Mutex::new(AccountFactory)); - - SYNC_ACCOUNT_FACTORY.lock().await - } - - /// Create new account and fund it via nitro test node access. - /// - /// # Errors - /// - /// May fail if unable to find the path to the node or if funding the newly - /// created account fails. - async fn create() -> eyre::Result { - let _lock = AccountFactory::lock().await; - - let signer = PrivateKeySigner::random(); - let addr = signer.address(); - fund_account(addr, DEFAULT_FUNDING_ETH)?; - - let rpc_url = std::env::var(RPC_URL_ENV_VAR_NAME) - .expect("failed to load RPC_URL var from env") - .parse() - .expect("failed to parse RPC_URL string into a URL"); - let wallet = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(EthereumWallet::from(signer.clone())) - .on_http(rpc_url); - - Ok(Account { signer, wallet }) - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs deleted file mode 100644 index e65aef6eb2..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/deploy.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::path::Path; - -use alloy::{rpc::types::TransactionReceipt, sol_types::SolConstructor}; -use koba::config::Deploy; - -use crate::project::Crate; - -/// A basic smart contract deployer. -pub struct Deployer { - rpc_url: String, - private_key: String, - ctr_args: Option, -} - -impl Deployer { - pub fn new(rpc_url: String, private_key: String) -> Self { - Self { rpc_url, private_key, ctr_args: None } - } - - /// Add solidity constructor to the deployer. - pub fn with_constructor( - mut self, - constructor: C, - ) -> Deployer { - self.ctr_args = Some(alloy::hex::encode(constructor.abi_encode())); - self - } - - /// Add the default constructor to the deployer. - pub fn with_default_constructor( - self, - ) -> Deployer { - self.with_constructor(C::default()) - } - - /// Deploy and activate the contract implemented as `#[entrypoint]` in the - /// current crate. - /// Consumes currently configured deployer. - /// - /// # Errors - /// - /// May error if: - /// - /// - Unable to collect information about the crate required for deployment. - /// - [`koba::deploy`] errors. - pub async fn deploy(self) -> eyre::Result { - let pkg = Crate::new()?; - let wasm_path = pkg.wasm; - let sol_path = pkg.manifest_dir.join("src/constructor.sol"); - let sol = - if Path::new(&sol_path).exists() { Some(sol_path) } else { None }; - - let config = Deploy { - generate_config: koba::config::Generate { - wasm: wasm_path.clone(), - sol, - args: self.ctr_args, - legacy: false, - }, - auth: koba::config::PrivateKey { - private_key_path: None, - private_key: Some(self.private_key), - keystore_path: None, - keystore_password_path: None, - }, - endpoint: self.rpc_url, - deploy_only: false, - quiet: false, - }; - koba::deploy(&config).await - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs deleted file mode 100644 index f0658dd421..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/environment.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::{path::PathBuf, process::Command}; - -use eyre::Context; - -/// Gets expected path to the nitro test node. -pub(crate) fn get_node_path() -> eyre::Result { - let manifest_dir = get_workspace_root()?; - Ok(manifest_dir.join("nitro-testnode")) -} - -/// Runs the following command to get the worskpace root: -/// -/// ```bash -/// dirname "$(cargo locate-project --workspace --message-format plain)" -/// ``` -pub(crate) fn get_workspace_root() -> eyre::Result { - let output = Command::new("cargo") - .arg("locate-project") - .arg("--workspace") - .arg("--message-format") - .arg("plain") - .output() - .wrap_err("should run `cargo locate-project`")?; - - let manifest_path = String::from_utf8_lossy(&output.stdout); - let manifest_dir = Command::new("dirname") - .arg(&*manifest_path) - .output() - .wrap_err("should run `dirname`")?; - - let path = String::from_utf8_lossy(&manifest_dir.stdout) - .trim() - .to_string() - .parse::() - .wrap_err("failed to parse manifest dir path")?; - Ok(path) -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs deleted file mode 100644 index 360ebd9cb2..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/error.rs +++ /dev/null @@ -1,116 +0,0 @@ -use alloy::{ - sol_types::SolError, - transports::{RpcError, TransportErrorKind}, -}; - -/// Possible panic codes for a revert. -/// -/// Taken from -#[derive(Debug)] -#[allow(missing_docs)] // Pretty straightforward variant names. -pub enum PanicCode { - AssertionError = 0x1, - ArithmeticOverflow = 0x11, - DivisionByZero = 0x12, - EnumConversionOutOfBounds = 0x21, - IncorrectlyEncodedStorageByteArray = 0x22, - PopOnEmptyArray = 0x31, - ArrayAccessOutOfBounds = 0x32, - TooMuchMemoryAllocated = 0x41, - ZeroInitializedVariable = 0x51, -} - -impl core::fmt::Display for PanicCode { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let msg = match self { - PanicCode::AssertionError => - "Assertion error", - PanicCode::ArithmeticOverflow => - "Arithmetic operation overflowed outside of an unchecked block", - PanicCode::DivisionByZero => - "Division or modulo division by zero", - PanicCode::EnumConversionOutOfBounds => - "Tried to convert a value into an enum, but the value was too big or negative", - PanicCode::IncorrectlyEncodedStorageByteArray => - "Incorrectly encoded storage byte array", - PanicCode::PopOnEmptyArray => - ".pop() was called on an empty array", - PanicCode::ArrayAccessOutOfBounds => - "Array accessed at an out-of-bounds or negative index", - PanicCode::TooMuchMemoryAllocated => - "Too much memory was allocated, or an array was created that is too large", - PanicCode::ZeroInitializedVariable => - "Called a zero-initialized variable of internal function type" - }; - - write!(f, "{}", msg) - } -} - -/// An error representing a panic. -pub trait Panic { - /// Checks that `Self` corresponds to a panic with code `code`. - fn panicked_with(&self, code: PanicCode) -> bool; -} - -/// An error representing a revert with some data. -pub trait Revert { - /// Checks that `Self` corresponds to the typed abi-encoded error - /// `expected`. - fn reverted_with(&self, expected: E) -> bool; -} - -impl Panic for alloy::contract::Error { - fn panicked_with(&self, _code: PanicCode) -> bool { - let Self::TransportError(e) = self else { - return false; - }; - - // FIXME: right now we cannot have any better error code for Panics - // check `e`: - // ErrorResp( - // ErrorPayload { - // code: -32000, - // message: "execution reverted", - // data: None, - // }, - // ) - let payload = e.as_error_resp().expect("should contain payload"); - payload.code == -32000 && payload.message == "execution reverted" - } -} - -impl Revert for alloy::contract::Error { - fn reverted_with(&self, expected: E) -> bool { - let Self::TransportError(e) = self else { - return false; - }; - - let raw_value = e - .as_error_resp() - .and_then(|payload| payload.data.clone()) - .expect("should extract the error"); - let actual = &raw_value.get().trim_matches('"')[2..]; - let expected = alloy::hex::encode(expected.abi_encode()); - expected == actual - } -} - -impl Revert for eyre::Report { - fn reverted_with(&self, expected: E) -> bool { - let Some(received) = self - .chain() - .find_map(|err| err.downcast_ref::>()) - else { - return false; - }; - let RpcError::ErrorResp(received) = received else { - return false; - }; - let Some(received) = &received.data else { - return false; - }; - let expected = alloy::hex::encode(expected.abi_encode()); - received.to_string().contains(&expected) - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs deleted file mode 100644 index 5d6cd76a39..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/event.rs +++ /dev/null @@ -1,23 +0,0 @@ -use alloy::{rpc::types::eth::TransactionReceipt, sol_types::SolEvent}; - -/// Extension trait for asserting an event gets emitted. -pub trait EventExt { - /// Asserts the contract emitted the `expected` event. - fn emits(&self, expected: E) -> bool; -} - -impl EventExt for TransactionReceipt -where - E: SolEvent, - E: PartialEq, -{ - fn emits(&self, expected: E) -> bool { - // Extract all events that are the expected type. - self.inner - .logs() - .iter() - .filter_map(|log| log.log_decode().ok()) - .map(|log| log.inner.data) - .any(|event| expected == event) - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs deleted file mode 100644 index 5a89cc9aac..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/lib.rs +++ /dev/null @@ -1,99 +0,0 @@ -#![doc = include_str!("../README.md")] -mod account; -mod deploy; -mod environment; -mod error; -mod event; -mod project; -mod receipt; -mod system; - -pub use account::Account; -pub use e2e_proc::test; -pub use error::{Panic, PanicCode, Revert}; -pub use event::EventExt; -pub use receipt::ReceiptExt; -pub use system::{fund_account, provider, Provider, Wallet}; -use eyre::WrapErr; - -/// Load the `name` environment variable. -pub fn env(name: &str) -> eyre::Result { - std::env::var(name).wrap_err(format!("failed to load {name}")) -} - -/// This macro provides a shorthand for broadcasting the transaction to the -/// network. -/// -/// See: -/// -/// # Examples -/// -/// ```rust,ignore -/// #[e2e::test] -/// async fn foo(alice: Account) -> eyre::Result<()> { -/// let contract_addr = alice.as_deployer().deploy().await?.address()?; -/// let contract = Erc721::new(contract_addr, &alice.wallet); -/// -/// let alice_addr = alice.address(); -/// let token_id = random_token_id(); -/// let pending_tx = send!(contract.mint(alice_addr, token_id))?; -/// // ... -/// } -#[macro_export] -macro_rules! send { - ($e:expr) => { - $e.send().await - }; -} - -/// This macro provides a shorthand for broadcasting the transaction -/// to the network, and then waiting for the given number of confirmations. -/// -/// See: -/// -/// # Examples -/// -/// ```rust,ignore -/// #[e2e::test] -/// async fn foo(alice: Account) -> eyre::Result<()> { -/// let contract_addr = alice.as_deployer().deploy().await?.address()?; -/// let contract = Erc721::new(contract_addr, &alice.wallet); -/// -/// let alice_addr = alice.address(); -/// let token_id = random_token_id(); -/// let result = watch!(contract.mint(alice_addr, token_id))?; -/// // ... -/// } -#[macro_export] -macro_rules! watch { - ($e:expr) => { - $crate::send!($e)?.watch().await - }; -} - -/// This macro provides a shorthand for broadcasting the transaction -/// to the network, waiting for the given number of confirmations, and then -/// fetching the transaction receipt. -/// -/// See: -/// -/// # Examples -/// -/// ```rust,ignore -/// #[e2e::test] -/// async fn foo(alice: Account) -> eyre::Result<()> { -/// let contract_addr = alice.as_deployer().deploy().await?.address()?; -/// let contract = Erc721::new(contract_addr, &alice.wallet); -/// -/// let alice_addr = alice.address(); -/// let token_id = random_token_id(); -/// let receipt = receipt!(contract.mint(alice_addr, token_id))?; -/// // ... -/// } -#[macro_export] -macro_rules! receipt { - ($e:expr) => { - $crate::send!($e)?.get_receipt().await - }; -} - diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs deleted file mode 100644 index 537d8fa597..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/project.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{ - env, - ffi::OsStr, - fs::File, - io::{BufReader, Read}, - path::{Path, PathBuf}, -}; - -use eyre::bail; -use toml::Table; - -/// Information about the crate subject of an integration test. -pub(crate) struct Crate { - /// Path to the directory where the crate's manifest lives. - pub(crate) manifest_dir: PathBuf, - /// Path to the compiled wasm binary. - pub(crate) wasm: PathBuf, -} - -impl Crate { - /// Collects crate information from the environment. - /// - /// # Errors - /// - /// May error if: - /// - /// - The current working directory is invalid. - /// - Could not read the package name from the manifest file. - /// - Could not read the path to the compiled wasm binary. - pub(crate) fn new() -> eyre::Result { - let manifest_dir = env::current_dir()?; - let name = read_pkg_name(&manifest_dir)?; - let wasm = get_wasm(&name)?; - - Ok(Self { manifest_dir, wasm }) - } -} - -/// Reads and parses the package name from a manifest in `path`. -/// -/// # Errors -/// -/// May error if: -/// -/// - Unable to parse the `Cargo.toml` at `path`. -/// - Unable to read the package name from the parsed toml file. -fn read_pkg_name>(path: P) -> eyre::Result { - let cargo_toml = path.as_ref().join("Cargo.toml"); - - let mut reader = BufReader::new(File::open(cargo_toml)?); - let mut buffer = String::new(); - reader.read_to_string(&mut buffer)?; - - let table = buffer.parse::
()?; - let name = table["package"]["name"].as_str(); - - match name { - Some(x) => Ok(x.to_owned()), - None => Err(eyre::eyre!("unable to find package name in toml")), - } -} - -/// Returns the path to the compiled wasm binary with name `name`. -/// -/// Note that this function works for both workspaces and standalone crates. -/// -/// # Errors -/// -/// May error if: -/// -/// - Unable to read the current executable's path. -/// - The output directory is not `target`. -fn get_wasm(name: &str) -> eyre::Result { - let name = name.replace('-', "_"); - // Looks like - // "rust-contracts-stylus/target/debug/deps/erc721-15764c2c9a33bee7". - let mut target_dir = env::current_exe()?; - - // Recursively find a `target` directory. - loop { - let Some(parent) = target_dir.parent() else { - // We've found `/`. - bail!("output directory is not 'target'"); - }; - - target_dir = parent.to_path_buf(); - let Some(leaf) = target_dir.file_name() else { - // We've found the root because we are traversing a canonicalized - // path, which means there are no `..` segments, and we started at - // the executable. - bail!("output directory is not 'target'"); - }; - - if leaf == OsStr::new("target") { - break; - } - } - - let wasm = target_dir - .join("wasm32-unknown-unknown") - .join("release") - .join(format!("{name}.wasm")); - - Ok(wasm) -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs deleted file mode 100644 index 631a250ef1..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/receipt.rs +++ /dev/null @@ -1,17 +0,0 @@ -use alloy::{ - network::ReceiptResponse, primitives::Address, - rpc::types::TransactionReceipt, -}; -use eyre::ContextCompat; - -/// Extension trait to recover address of the contract that was deployed. -pub trait ReceiptExt { - /// Returns the address of the contract from the [`TransactionReceipt`]. - fn address(&self) -> eyre::Result
; -} - -impl ReceiptExt for TransactionReceipt { - fn address(&self) -> eyre::Result
{ - self.contract_address().context("should contain contract address") - } -} diff --git a/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs b/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs deleted file mode 100644 index 5d90cd8be5..0000000000 --- a/target_chains/ethereum/sdk/stylus/lib/e2e/src/system.rs +++ /dev/null @@ -1,85 +0,0 @@ -use alloy::{ - network::{Ethereum, EthereumWallet}, - primitives::Address, - providers::{ - fillers::{ - ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, - WalletFiller, - }, - Identity, ProviderBuilder, RootProvider, - }, - transports::http::{Client, Http}, -}; -use eyre::{bail, Context}; - -use crate::environment::get_node_path; - -pub(crate) const RPC_URL_ENV_VAR_NAME: &str = "RPC_URL"; - -/// Convenience type alias that represents an Ethereum wallet. -pub type Wallet = FillProvider< - JoinFill< - JoinFill< - JoinFill, NonceFiller>, - ChainIdFiller, - >, - WalletFiller, - >, - RootProvider>, - Http, - Ethereum, ->; - -/// Convenience type alias that represents an alloy provider. -pub type Provider = FillProvider< - JoinFill< - JoinFill, NonceFiller>, - ChainIdFiller, - >, - RootProvider>, - Http, - Ethereum, ->; - -/// Load the `name` environment variable. -fn env(name: &str) -> eyre::Result { - std::env::var(name).wrap_err(format!("failed to load {name}")) -} - -/// Returns an alloy provider connected to the `RPC_URL` rpc endpoint. -/// -/// # Panics -/// -/// May panic if unable to load the `RPC_URL` environment variable. -#[must_use] -pub fn provider() -> Provider { - let rpc_url = env(RPC_URL_ENV_VAR_NAME) - .expect("failed to load RPC_URL var from env") - .parse() - .expect("failed to parse RPC_URL string into a URL"); - ProviderBuilder::new().with_recommended_fillers().on_http(rpc_url) -} - -/// Send `amount` eth to `address` in the nitro-tesnode. -pub fn fund_account(address: Address, amount: u32) -> eyre::Result<()> { - let node_script = get_node_path()?.join("test-node.bash"); - if !node_script.exists() { - bail!("Test nitro node wasn't setup properly. Try to setup it first with `./scripts/nitro-testnode.sh -i -d`") - }; - - let output = std::process::Command::new(node_script) - .arg("script") - .arg("send-l2") - .arg("--to") - .arg(format!("address_{address}")) - .arg("--ethamount") - .arg(amount.to_string()) - .output()?; - - if !output.status.success() { - let err = String::from_utf8_lossy(&output.stderr); - bail!("account's wallet wasn't funded - address is {address}:\n{err}") - } - - Ok(()) -} From b2ab6db1ae6cb03ec10559f229eabd3e54ffd283 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 12 Nov 2024 06:26:26 +0000 Subject: [PATCH 115/160] chore: changes --- .../sdk/stylus/benches/src/proxy_calls.rs | 4 +-- .../pyth-solidity/script/MockPyth.s.sol | 3 +- .../ethereum/sdk/stylus/scripts/bench.sh | 33 ++++++++++--------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index e3921a7e27..2be8b574de 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -60,7 +60,7 @@ pub async fn run_with( let time_frame = uint!(10000_U256); let age = uint!(10000_U256); - let (data, _ids) = create_price_feed_update_data_list(); + let (data, ids) = create_price_feed_update_data_list(); let _ = receipt!(contract.getPriceUnsafe(id))?; @@ -72,7 +72,7 @@ pub async fn run_with( let _ = receipt!(contract.updatePriceFeeds(data.clone()))?; //println!("{data:?} , {ids:?} "); - // let _ = receipt!(contract.updatePriceFeedsIfNecessary(data.clone(),ids.clone(), vec![i64::MAX,i64::MAX,i64::MAX]))?; + let _ = contract.updatePriceFeedsIfNecessary(data.clone(),ids.clone(), vec![100,100,100]).send().await?; // IMPORTANT: Order matters! use ProxyCall::*; diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index 6d2ed20eeb..e52867eaf2 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -40,7 +40,8 @@ contract MockPythScript is Script { int32 expo = int32(uint32(_deriveRandom(randomBase, 3) % 40)); int64 emaPrice = int64(uint64(_deriveRandom(randomBase, 4) % 10)); uint64 emaConf = uint64(_deriveRandom(randomBase, 5) % 10); - + string memory str = string(abi.encodePacked("block.timestamp :",uint256(block.timestamp))); + console.log(str); priceData[i] = pyth_contract.createPriceFeedUpdateData( priceIds[i], price, diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 4472394862..4627143eb4 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -14,26 +14,27 @@ cd "nitro-testnode" cd .. cd "pyth-solidity" -deployed_to=$( +# deployed_to=$( forge script ./script/MockPyth.s.sol:MockPythScript \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ - --broadcast \ - | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 -) + --broadcast + # \ + # | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 +#) -export MOCK_PYTH_ADDRESS=$deployed_to -cd .. -# Output the captured address +# export MOCK_PYTH_ADDRESS=$deployed_to +# cd .. +# # Output the captured address -NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} -cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort +# NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} +# cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort -# No need to compile benchmarks with `--release` -# since this only runs the benchmarking code and the contracts have already been compiled with `--release` -cargo run -p benches +# # No need to compile benchmarks with `--release` +# # since this only runs the benchmarking code and the contracts have already been compiled with `--release` +# cargo run -p benches -echo "NOTE: To measure non cached contract's gas usage correctly, - benchmarks should run on a clean instance of the nitro test node." -echo -echo "Finished running benches!" +# echo "NOTE: To measure non cached contract's gas usage correctly, +# benchmarks should run on a clean instance of the nitro test node." +# echo +# echo "Finished running benches!" From a610d2337e6606f9625296eb6e9f03697f4bb1f0 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 12 Nov 2024 06:26:34 +0000 Subject: [PATCH 116/160] forge install: forge-std v1.9.4 --- target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std index 8f24d6b04c..1eea5bae12 160000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std @@ -1 +1 @@ -Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 86a5d4a3fe0fe1c9413dfb0baccce149339b9284 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 12 Nov 2024 06:26:44 +0000 Subject: [PATCH 117/160] forge install: openzeppelin-foundry-upgrades v0.3.6 --- .gitmodules | 3 +++ .../sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades | 1 + 2 files changed, 4 insertions(+) create mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index f0585d7f91..99d198a5cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std"] path = target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades"] + path = target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000000..16e0ae21e0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 From 819f8a11d6b5dd7c8e5bbf8c90be8702548067a0 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 12 Nov 2024 06:30:52 +0000 Subject: [PATCH 118/160] forge install: openzeppelin-contracts v4.9.6 --- .gitmodules | 3 +++ .../sdk/stylus/pyth-solidity/lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 99d198a5cb..dc0b466582 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades"] path = target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts"] + path = target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts new file mode 160000 index 0000000000..dc44c9f1a4 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 From bc78af5f5023b48cb923d17c3fca8a6daa44368e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 14 Nov 2024 11:41:26 +0000 Subject: [PATCH 119/160] chore: formated code --- .../sdk/stylus/benches/src/function_calls.rs | 16 +- .../ethereum/sdk/stylus/benches/src/lib.rs | 4 +- .../ethereum/sdk/stylus/benches/src/main.rs | 8 +- .../sdk/stylus/benches/src/proxy_calls.rs | 31 +- .../ethereum/sdk/stylus/contracts/src/lib.rs | 3 +- .../sdk/stylus/contracts/src/pyth/errors.rs | 7 +- .../sdk/stylus/contracts/src/pyth/events.rs | 3 +- .../stylus/contracts/src/pyth/functions.rs | 91 +++--- .../sdk/stylus/contracts/src/pyth/mock.rs | 300 +++++++++++------- .../sdk/stylus/contracts/src/pyth/mod.rs | 2 - .../contracts/src/pyth/pyth_contract.rs | 154 ++++++--- .../sdk/stylus/contracts/src/pyth/types.rs | 131 +++++--- .../sdk/stylus/contracts/src/utils/helpers.rs | 26 +- .../stylus/examples/function-calls/src/lib.rs | 86 +++-- .../examples/function-calls/tests/abi/mod.rs | 4 +- .../function-calls/tests/function-calls.rs | 61 ++-- .../stylus/examples/proxy-calls/src/lib.rs | 6 +- .../examples/proxy-calls/tests/abi/mod.rs | 2 +- .../examples/proxy-calls/tests/proxy-calls.rs | 185 ++++++----- 19 files changed, 657 insertions(+), 463 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 19c3f79c01..3eba594781 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -7,9 +7,12 @@ use alloy::{ sol, sol_types::{SolCall, SolConstructor}, }; -use e2e::{receipt, Account, env}; +use e2e::{env, receipt, Account}; -use crate::{ report::{ContractReport, FunctionReport}, CacheOpt}; +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; sol!( #[sol(rpc)] @@ -21,7 +24,7 @@ sol!( function getUpdateFee() external returns (uint256 fee); function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds() external payable; - function updatePriceFeedsIfNecessary() external payable; + function updatePriceFeedsIfNecessary() external payable; } ); @@ -88,9 +91,12 @@ async fn deploy( ) -> eyre::Result
{ let pyth_addr = env("MOCK_PYTH_ADDRESS")?; let address = Address::from_str(&pyth_addr)?; - let id= keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); + let id = keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); let price_id = TypeFixedBytes::<32>::from_slice(&id); - let args = FunctionCallsExample::constructorCall { _pythAddress: address, _priceId: price_id }; + let args = FunctionCallsExample::constructorCall { + _pythAddress: address, + _priceId: price_id, + }; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "function-calls", Some(args), cache_opt).await } diff --git a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs index 6326844840..7103f3d7db 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/lib.rs @@ -13,9 +13,9 @@ use eyre::WrapErr; use koba::config::{Deploy, Generate, PrivateKey}; use serde::Deserialize; -pub mod report; pub mod function_calls; pub mod proxy_calls; +pub mod report; #[derive(Debug, Deserialize)] struct ArbOtherFields { @@ -49,7 +49,7 @@ async fn deploy( .join("target") .join("wasm32-unknown-unknown") .join("release") - .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); + .join(format!("{}_example.wasm", contract_name.replace('-', "_"))); let sol_path = args.as_ref().map(|_| { manifest_dir .join("examples") diff --git a/target_chains/ethereum/sdk/stylus/benches/src/main.rs b/target_chains/ethereum/sdk/stylus/benches/src/main.rs index 5996710809..868c6af12a 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/main.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/main.rs @@ -1,13 +1,11 @@ -use benches::{ - function_calls,proxy_calls,report::BenchmarkReport, -}; +use benches::{function_calls, proxy_calls, report::BenchmarkReport}; use futures::FutureExt; #[tokio::main] async fn main() -> eyre::Result<()> { let report = futures::future::try_join_all([ - function_calls::bench().boxed(), - proxy_calls::bench().boxed(), + function_calls::bench().boxed(), + proxy_calls::bench().boxed(), ]) .await? .into_iter() diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 2be8b574de..281d0ddad0 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -1,15 +1,18 @@ use std::str::FromStr; use alloy::{ - network::{AnyNetwork, EthereumWallet}, primitives::{uint, Address, FixedBytes as TypeFixedBytes}, - providers::ProviderBuilder, - sol, sol_types::{ SolCall, SolConstructor} + network::{AnyNetwork, EthereumWallet}, + primitives::{uint, Address, FixedBytes as TypeFixedBytes}, + providers::ProviderBuilder, + sol, + sol_types::{SolCall, SolConstructor}, }; -use e2e::{receipt, Account, env}; +use e2e::{env, receipt, Account}; use pyth_stylus::pyth::mock::create_price_feed_update_data_list; use crate::{ - report::{ContractReport, FunctionReport}, CacheOpt + report::{ContractReport, FunctionReport}, + CacheOpt, }; sol!( @@ -58,21 +61,27 @@ pub async fn run_with( let id = keccak_const::Keccak256::new().update(b"ETH").finalize().to_vec(); let id = TypeFixedBytes::<32>::from_slice(&id); let time_frame = uint!(10000_U256); - let age = uint!(10000_U256); - - let (data, ids) = create_price_feed_update_data_list(); + let age = uint!(10000_U256); + let (data, ids) = create_price_feed_update_data_list(); let _ = receipt!(contract.getPriceUnsafe(id))?; let _ = receipt!(contract.getEmaPriceUnsafe(id))?; - let _ = receipt!(contract.getPriceNoOlderThan(id,age))?; - let _ = receipt!(contract.getEmaPriceNoOlderThan(id,age))?; + let _ = receipt!(contract.getPriceNoOlderThan(id, age))?; + let _ = receipt!(contract.getEmaPriceNoOlderThan(id, age))?; let _ = receipt!(contract.getValidTimePeriod())?; let _ = receipt!(contract.getUpdateFee(data.clone()))?; let _ = receipt!(contract.updatePriceFeeds(data.clone()))?; //println!("{data:?} , {ids:?} "); - let _ = contract.updatePriceFeedsIfNecessary(data.clone(),ids.clone(), vec![100,100,100]).send().await?; + let _ = contract + .updatePriceFeedsIfNecessary( + data.clone(), + ids.clone(), + vec![100, 100, 100], + ) + .send() + .await?; // IMPORTANT: Order matters! use ProxyCall::*; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs index 76aad27ec6..e9ba86fa6d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/lib.rs @@ -16,9 +16,8 @@ pub mod utils; /// This module contains the types and functions for interacting with the Pyth oracle. pub mod pyth; - #[cfg(target_arch = "wasm32")] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} -} \ No newline at end of file +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs index e81a2eb45c..e1f2899b4c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -1,8 +1,5 @@ -use stylus_sdk::{ - call::MethodError, - prelude::*, -}; use alloy_sol_types::sol; +use stylus_sdk::{call::MethodError, prelude::*}; sol! { // Function arguments are invalid (e.g., the arguments lengths mismatch) @@ -92,7 +89,7 @@ sol! { #[derive(Debug)] #[allow(missing_docs)] error FalledDecodeData(); - + } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs index 04a4b12e7f..fe2248df08 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/events.rs @@ -1,7 +1,6 @@ use alloy_sol_types::sol; - -sol! { +sol! { event PriceFeedUpdate( bytes32 indexed id, uint64 publishTime, diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index 91aacca50d..488fdb8212 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -1,23 +1,15 @@ +use crate::pyth::mock::DecodeDataType; use crate::pyth::types::{ - getEmaPriceUnsafeCall, - getEmaPriceNoOlderThanCall, - getPriceUnsafeCall, - getValidTimePeriodCall, - getUpdateFeeCall, - getPriceNoOlderThanCall, - parsePriceFeedUpdatesCall, - parsePriceFeedUpdatesUniqueCall, - updatePriceFeedsIfNecessaryCall, - updatePriceFeedsCall, - Price, - PriceFeed + getEmaPriceNoOlderThanCall, getEmaPriceUnsafeCall, getPriceNoOlderThanCall, + getPriceUnsafeCall, getUpdateFeeCall, getValidTimePeriodCall, + parsePriceFeedUpdatesCall, parsePriceFeedUpdatesUniqueCall, + updatePriceFeedsCall, updatePriceFeedsIfNecessaryCall, Price, PriceFeed, }; use crate::utils::helpers::{call_helper, delegate_call_helper}; use alloc::vec::Vec; -use stylus_sdk::storage::TopLevelStorage; -use alloy_primitives::{ Address, B256, U256, Bytes}; -use crate::pyth::mock::DecodeDataType; +use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_sol_types::SolType; +use stylus_sdk::storage::TopLevelStorage; /// Retrieves the price for a given asset ID from the Pyth price feed, ensuring the price is not older than a specified age. /// @@ -35,7 +27,11 @@ pub fn get_price_no_older_than( id: B256, age: U256, ) -> Result> { - let price_call = call_helper::(storage, pyth_address, (id, age,))?; + let price_call = call_helper::( + storage, + pyth_address, + (id, age), + )?; Ok(price_call.price) } @@ -53,7 +49,8 @@ pub fn get_update_fee( pyth_address: Address, update_data: Vec, ) -> Result> { - let update_fee_call = call_helper::(storage, pyth_address, (update_data,))?; + let update_fee_call = + call_helper::(storage, pyth_address, (update_data,))?; Ok(update_fee_call.feeAmount) } @@ -71,7 +68,8 @@ pub fn get_ema_price_unsafe( pyth_address: Address, id: B256, ) -> Result> { - let ema_price = call_helper::(storage, pyth_address, (id,))?; + let ema_price = + call_helper::(storage, pyth_address, (id,))?; Ok(ema_price.price) } @@ -91,7 +89,11 @@ pub fn get_ema_price_no_older_than( id: B256, age: U256, ) -> Result> { - let ema_price = call_helper::(storage, pyth_address, (id, age,))?; + let ema_price = call_helper::( + storage, + pyth_address, + (id, age), + )?; Ok(ema_price.price) } @@ -109,7 +111,8 @@ pub fn get_price_unsafe( pyth_address: Address, id: B256, ) -> Result> { - let price = call_helper::(storage, pyth_address, (id,))?; + let price = + call_helper::(storage, pyth_address, (id,))?; let price = Price { price: price._0, conf: price._1, @@ -131,7 +134,8 @@ pub fn get_valid_time_period( storage: &mut impl TopLevelStorage, pyth_address: Address, ) -> Result> { - let valid_time_period = call_helper::(storage, pyth_address, ())?; + let valid_time_period = + call_helper::(storage, pyth_address, ())?; Ok(valid_time_period.validTimePeriod) } @@ -149,7 +153,11 @@ pub fn update_price_feeds( pyth_address: Address, update_data: Vec, ) -> Result<(), Vec> { - delegate_call_helper::(storage, pyth_address, (update_data,))?; + delegate_call_helper::( + storage, + pyth_address, + (update_data,), + )?; Ok(()) } @@ -199,34 +207,44 @@ pub fn parse_price_feed_updates( min_publish_time: u64, max_publish_time: u64, ) -> Result, Vec> { - let parse_price_feed_updates_call = delegate_call_helper::( - storage, - pyth_address, - (update_data, price_ids, min_publish_time, max_publish_time), - )?; + let parse_price_feed_updates_call = + delegate_call_helper::( + storage, + pyth_address, + (update_data, price_ids, min_publish_time, max_publish_time), + )?; Ok(parse_price_feed_updates_call.priceFeeds) } +/// Similar to `parse_price_feed_updates`, but only returns the latest price feed for each asset if multiple updates are available. +/// /// # Parameters /// - `storage`: A mutable reference to an implementation of `TopLevelStorage`. /// - `pyth_address`: The address of the Pyth price feed contract. /// - `update_data`: A vector of bytes containing the data required for the updates. /// - `price_ids`: A vector of fixed byte identifiers for the assets being updated. -/// - `min_publish_time`: The minimum publish time to consider for the updates -/// Parses the price feed updates uniquely and returns the corresponding price feeds. +/// - `min_publish_time`: The minimum publish time to consider for the updates. +/// - `max_publish_time`: The maximum publish time to consider for the updates. +/// +/// # Returns +/// - `Result, Vec>`: A `Result` containing a vector of `PriceFeed` structs if successful, or an error message as a byte vector. pub fn parse_price_feed_updates_unique( storage: &mut impl TopLevelStorage, pyth_address: Address, update_data: Vec, price_ids: Vec, min_publish_time: u64, - max_publish_time: u64 + max_publish_time: u64, ) -> Result, Vec> { - let parse_price_feed_updates_call = delegate_call_helper::(storage, pyth_address, (update_data, price_ids, min_publish_time, max_publish_time))?; + let parse_price_feed_updates_call = + delegate_call_helper::( + storage, + pyth_address, + (update_data, price_ids, min_publish_time, max_publish_time), + )?; Ok(parse_price_feed_updates_call.priceFeeds) } - /// Creates the update data required for a price feed, encapsulating the current price, confidence interval, /// exponential moving average (EMA) price, and other relevant details. /// @@ -253,13 +271,10 @@ pub fn create_price_feed_update_data( prev_publish_time: u64, ) -> Vec { let price = Price { price, conf, expo, publish_time }; - let ema_price = Price { price: ema_price, conf: ema_conf, expo, publish_time }; + let ema_price = + Price { price: ema_price, conf: ema_conf, expo, publish_time }; - let price_feed_data = PriceFeed { - id, - price, - ema_price, - }; + let price_feed_data = PriceFeed { id, price, ema_price }; let price_feed_data_encoding = (price_feed_data, prev_publish_time); return DecodeDataType::abi_encode(&price_feed_data_encoding); diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index aa7f7da738..fed01785d2 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -1,12 +1,15 @@ -use alloc::vec::Vec; -use alloy_primitives::{Uint, U256, Bytes, B256}; -use alloy_sol_types::{ sol_data::Uint as SolUInt, SolType, SolValue}; -use stylus_sdk::{abi::Bytes as AbiBytes,evm, msg, prelude::*}; -use crate::{pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE}; use crate::pyth::errors::{Error, PriceFeedNotFound}; -use crate::pyth::types::{ PriceFeed,Price, StoragePriceFeed}; use crate::pyth::events::PriceFeedUpdate; use crate::pyth::functions::create_price_feed_update_data; +use crate::pyth::types::{Price, PriceFeed, StoragePriceFeed}; +use crate::{ + pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, + utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE, +}; +use alloc::vec::Vec; +use alloy_primitives::{Bytes, Uint, B256, U256}; +use alloy_sol_types::{sol_data::Uint as SolUInt, SolType, SolValue}; +use stylus_sdk::{abi::Bytes as AbiBytes, evm, msg, prelude::*}; ///Decode data type PriceFeed and uint64 pub type DecodeDataType = (PriceFeed, SolUInt<64>); @@ -21,31 +24,35 @@ sol_storage! { #[public] impl MockPythContract { - - fn initialize(&mut self, single_update_fee_in_wei: Uint<256, 4>, valid_time_period: Uint<256, 4>) -> Result<(), Vec> { - if single_update_fee_in_wei <= U256::from(0) || valid_time_period <= U256::from(0) { + fn initialize( + &mut self, + single_update_fee_in_wei: Uint<256, 4>, + valid_time_period: Uint<256, 4>, + ) -> Result<(), Vec> { + if single_update_fee_in_wei <= U256::from(0) + || valid_time_period <= U256::from(0) + { return Err(Error::InvalidArgument(InvalidArgument {}).into()); - } + } self.single_update_fee_in_wei.set(single_update_fee_in_wei); self.valid_time_period.set(valid_time_period); Ok(()) } - fn query_price_feed(&self, id: B256) -> Result, Vec> { - let price_feed = self.price_feeds.get(id).to_price_feed(); + let price_feed = self.price_feeds.get(id).to_price_feed(); if price_feed.id.eq(&B256::ZERO) { return Err(Error::PriceFeedNotFound(PriceFeedNotFound {}).into()); } Ok(price_feed.abi_encode()) } - fn price_feed_exists(&self, id:B256) -> bool { - self.price_feeds.getter(id).id.is_empty() + fn price_feed_exists(&self, id: B256) -> bool { + self.price_feeds.getter(id).id.is_empty() } fn get_valid_time_period(&self) -> Uint<256, 4> { - self.valid_time_period.get() + self.valid_time_period.get() } // Takes an array of encoded price feeds and stores them. @@ -65,160 +72,197 @@ impl MockPythContract { // ] #[payable] - fn update_price_feeds(&mut self, - update_data : Vec + fn update_price_feeds( + &mut self, + update_data: Vec, ) -> Result<(), Vec> { - let required_fee = self.get_update_fee(update_data.clone()); - if required_fee.lt(&U256::from(msg::value())) { - return Err(Error::InsufficientFee(InsufficientFee {}).into()); + let required_fee = self.get_update_fee(update_data.clone()); + if required_fee.lt(&U256::from(msg::value())) { + return Err(Error::InsufficientFee(InsufficientFee {}).into()); } for i in 0..update_data.len() { - let price_feed_data = ::abi_decode(&update_data[i], false) - .map_err( |_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec())?; - let last_publish_time = &self.price_feeds.get(price_feed_data.id).price.publish_time; - if last_publish_time.lt(&price_feed_data.price.publish_time) { - self.price_feeds.setter(price_feed_data.id).set(price_feed_data); - evm::log(PriceFeedUpdate { - id: price_feed_data.id, - publishTime: price_feed_data.price.publish_time.to(), - price: price_feed_data.price.price, - conf: price_feed_data.price.conf - }); - } + let price_feed_data = + ::abi_decode(&update_data[i], false) + .map_err(|_| { + CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec() + })?; + let last_publish_time = + &self.price_feeds.get(price_feed_data.id).price.publish_time; + if last_publish_time.lt(&price_feed_data.price.publish_time) { + self.price_feeds + .setter(price_feed_data.id) + .set(price_feed_data); + evm::log(PriceFeedUpdate { + id: price_feed_data.id, + publishTime: price_feed_data.price.publish_time.to(), + price: price_feed_data.price.price, + conf: price_feed_data.price.conf, + }); + } } - Ok(()) + Ok(()) } fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { - self.single_update_fee_in_wei.get() * U256::from(update_data.len()) + self.single_update_fee_in_wei.get() * U256::from(update_data.len()) } - #[payable] + #[payable] fn parse_price_feed_updates( &mut self, - update_data:Vec, - price_ids:Vec, - min_publish_time:u64, - max_publish_time:u64 - ) -> Result, Vec>{ - self.parse_price_feed_updates_internal(update_data, price_ids, min_publish_time, max_publish_time, false) + update_data: Vec, + price_ids: Vec, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Vec> { + self.parse_price_feed_updates_internal( + update_data, + price_ids, + min_publish_time, + max_publish_time, + false, + ) } #[payable] fn parse_price_feed_updates_unique( &mut self, - update_data:Vec, - price_ids:Vec, - min_publish_time:u64, - max_publish_time:u64 - ) -> Result, Vec>{ - self.parse_price_feed_updates_internal(update_data, price_ids, min_publish_time, max_publish_time, true) + update_data: Vec, + price_ids: Vec, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, Vec> { + self.parse_price_feed_updates_internal( + update_data, + price_ids, + min_publish_time, + max_publish_time, + true, + ) } - fn create_price_feed_update_data(&self, - id:B256, - price:i64, - conf:u64, - expo:i32, - ema_price:i64, - ema_conf:u64, - publish_time:U256, - prev_publish_time:u64 + fn create_price_feed_update_data( + &self, + id: B256, + price: i64, + conf: u64, + expo: i32, + ema_price: i64, + ema_conf: u64, + publish_time: U256, + prev_publish_time: u64, ) -> Vec { - let price = Price { price: price, conf, expo, publish_time }; - let ema_price = Price { price:ema_price, conf:ema_conf, expo:expo,publish_time}; - - let price_feed_data = PriceFeed { - id,price,ema_price - }; - + let price = Price { price, conf, expo, publish_time }; + let ema_price = + Price { price: ema_price, conf: ema_conf, expo, publish_time }; + + let price_feed_data = PriceFeed { id, price, ema_price }; + let price_feed_data_encoding = (price_feed_data, prev_publish_time); - return DecodeDataType::abi_encode(&price_feed_data_encoding); + return DecodeDataType::abi_encode(&price_feed_data_encoding); } } -impl MockPythContract { - - fn parse_price_feed_updates_internal(&mut self, +impl MockPythContract { + fn parse_price_feed_updates_internal( + &mut self, update_data: Vec, price_ids: Vec, - min_publish_time:u64, - max_publish_time:u64, - unique:bool - ) -> Result, Vec> { - let required_fee = self.get_update_fee(update_data.clone()); + min_publish_time: u64, + max_publish_time: u64, + unique: bool, + ) -> Result, Vec> { + let required_fee = self.get_update_fee(update_data.clone()); if required_fee.lt(&U256::from(msg::value())) { - return Err(Error::InsufficientFee(InsufficientFee {}).into()); + return Err(Error::InsufficientFee(InsufficientFee {}).into()); } let mut feeds = Vec::::with_capacity(price_ids.len()); - + for i in 0..price_ids.len() { - for j in 0..update_data.len() { - let (price_feed, prev_publish_time) = match DecodeDataType::abi_decode(&update_data[j], false) { - Ok(res) =>{ res }, - Err(_) => { - return Err(Error::FalledDecodeData(FalledDecodeData {}).into()) - } - }; + for j in 0..update_data.len() { + let (price_feed, prev_publish_time) = + match DecodeDataType::abi_decode(&update_data[j], false) { + Ok(res) => res, + Err(_) => { + return Err(Error::FalledDecodeData( + FalledDecodeData {}, + ) + .into()) + } + }; feeds[j] = price_feed.clone(); let publish_time = price_feed.price.publish_time; - if self.price_feeds.get(feeds[i].id).price.publish_time.lt(&publish_time) { - self.price_feeds.setter(feeds[i].id).set(feeds[i]); - evm::log( - PriceFeedUpdate { - id:feeds[i].id, - publishTime: publish_time.to(), - price: feeds[i].price.price, - conf: feeds[i].price.conf - }); + if self + .price_feeds + .get(feeds[i].id) + .price + .publish_time + .lt(&publish_time) + { + self.price_feeds.setter(feeds[i].id).set(feeds[i]); + evm::log(PriceFeedUpdate { + id: feeds[i].id, + publishTime: publish_time.to(), + price: feeds[i].price.price, + conf: feeds[i].price.conf, + }); } - if feeds[i].id == price_ids[i] { - if publish_time.gt(&U256::from(min_publish_time)) - && publish_time.le(&U256::from(max_publish_time)) && - (!unique || prev_publish_time.lt(&min_publish_time)) - { + if publish_time.gt(&U256::from(min_publish_time)) + && publish_time.le(&U256::from(max_publish_time)) + && (!unique || prev_publish_time.lt(&min_publish_time)) + { break; - } else { - feeds[i].id = B256::ZERO; - } + } else { + feeds[i].id = B256::ZERO; + } } - } if feeds[i].id != price_ids[i] { - return Err(Error::FalledDecodeData(FalledDecodeData {}).into()) + return Err(Error::FalledDecodeData(FalledDecodeData {}).into()); } } - Ok(feeds.abi_encode()) - } + Ok(feeds.abi_encode()) + } } pub fn create_price_feed_update_data_list() -> (Vec, Vec) { - let id = ["ETH","SOL","BTC"].map(|x| { - let x = keccak_const::Keccak256::new().update(x.as_bytes()).finalize().to_vec(); - return B256::from_slice(&x); + let id = ["ETH", "SOL", "BTC"].map(|x| { + let x = keccak_const::Keccak256::new() + .update(x.as_bytes()) + .finalize() + .to_vec(); + return B256::from_slice(&x); }); let mut price_feed_data_list = Vec::new(); for i in 0..3 { - let price_feed_data = create_price_feed_update_data( id[i],100,100,100,100,100,U256::from(U256::MAX - U256::from(10)),0); + let price_feed_data = create_price_feed_update_data( + id[i], + 100, + 100, + 100, + 100, + 100, + U256::from(U256::MAX - U256::from(10)), + 0, + ); let price_feed_data = Bytes::from(AbiBytes::from(price_feed_data).0); price_feed_data_list.push(price_feed_data); } - return (price_feed_data_list, id.to_vec()) + return (price_feed_data_list, id.to_vec()); } #[cfg(all(test, feature = "std"))] mod tests { + use crate::pyth::mock::{DecodeDataType, MockPythContract}; use alloc::vec; - use alloy_primitives::{ U256, B256}; - use stylus_sdk::abi::Bytes; - use - crate::pyth::mock::{MockPythContract, DecodeDataType}; + use alloy_primitives::{B256, U256}; use alloy_sol_types::SolType; + use stylus_sdk::abi::Bytes; // Updated constants to use uppercase naming convention const PRICE: i64 = 1000; @@ -241,8 +285,8 @@ mod tests { #[motsu::test] fn error_initialize_mock_contract(contract: MockPythContract) { - let err = contract.initialize(U256::from(0), U256::from(0)) ; - assert!(err.is_err()) + let err = contract.initialize(U256::from(0), U256::from(0)); + assert!(err.is_err()) } #[motsu::test] @@ -250,8 +294,18 @@ mod tests { let _ = contract.initialize(U256::from(1000), U256::from(1000)); let id = generate_bytes(); let publish_time = U256::from(1000); - let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, publish_time, PREV_PUBLISH_TIME); - let price_feed_decoded = DecodeDataType::abi_decode(&price_feed_created, true).unwrap(); + let price_feed_created = contract.create_price_feed_update_data( + id, + PRICE, + CONF, + EXPO, + EMA_PRICE, + EMA_CONF, + publish_time, + PREV_PUBLISH_TIME, + ); + let price_feed_decoded = + DecodeDataType::abi_decode(&price_feed_created, true).unwrap(); assert_eq!(price_feed_decoded.0.id, id); assert_eq!(price_feed_decoded.0.price.price, PRICE); assert_eq!(price_feed_decoded.0.price.conf, CONF); @@ -269,7 +323,16 @@ mod tests { let mut x = 0; while x < 10 { let id = generate_bytes(); - let price_feed_created = contract.create_price_feed_update_data(id, PRICE, CONF, EXPO, EMA_PRICE, EMA_CONF, publish_time, PREV_PUBLISH_TIME); + let price_feed_created = contract.create_price_feed_update_data( + id, + PRICE, + CONF, + EXPO, + EMA_PRICE, + EMA_CONF, + publish_time, + PREV_PUBLISH_TIME, + ); update_data.push(Bytes::from(price_feed_created)); x += 1; } @@ -289,7 +352,9 @@ mod tests { fn query_price_feed_failed(contract: MockPythContract) { let _ = contract.initialize(U256::from(1000), U256::from(1000)); let id = generate_bytes(); - let _price_feed = contract.query_price_feed(id).expect_err("should not query if price feed does not exist"); + let _price_feed = contract + .query_price_feed(id) + .expect_err("should not query if price feed does not exist"); } #[motsu::test] @@ -298,5 +363,4 @@ mod tests { let valid_time_period = contract.get_valid_time_period(); assert_eq!(valid_time_period, U256::from(1000)); } - } diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 8836d8dddf..205ab5da3a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -11,5 +11,3 @@ pub mod functions; /// Conttract for interacting with the Pyth oracle. pub mod pyth_contract; - - diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs index b3728860c6..749ac8d74d 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/pyth_contract.rs @@ -1,18 +1,14 @@ -use alloy_sol_types::SolValue; -use stylus_sdk::{abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage}; -use alloc::vec::Vec; -use alloy_primitives::{ U256 , Bytes, B256 }; use crate::pyth::functions::{ - get_price_unsafe, - get_price_no_older_than, - get_ema_price_unsafe, - get_ema_price_no_older_than, - get_valid_time_period, - update_price_feeds, - update_price_feeds_if_necessary, - get_update_fee, - parse_price_feed_updates, - parse_price_feed_updates_unique + get_ema_price_no_older_than, get_ema_price_unsafe, get_price_no_older_than, + get_price_unsafe, get_update_fee, get_valid_time_period, + parse_price_feed_updates, parse_price_feed_updates_unique, + update_price_feeds, update_price_feeds_if_necessary, +}; +use alloc::vec::Vec; +use alloy_primitives::{Bytes, B256, U256}; +use alloy_sol_types::SolValue; +use stylus_sdk::{ + abi::Bytes as AbiBytes, prelude::*, storage::TopLevelStorage, }; /// `IPyth` is a trait that defines methods for interacting with the Pyth contract. pub trait IPyth { @@ -21,7 +17,7 @@ pub trait IPyth { type Error: Into>; /// Retrieves the latest price feed without any recency checks. - /// + /// /// # Parameters /// - `id`: The unique identifier for the price feed. /// @@ -30,33 +26,44 @@ pub trait IPyth { fn get_price_unsafe(&mut self, id: B256) -> Result, Self::Error>; /// Retrieves a price that is no older than a specified `age`. - /// + /// /// # Parameters /// - `id`: The unique identifier for the price feed. /// - `age`: The maximum acceptable age of the price in seconds. /// /// # Returns /// - `Result, Self::Error>`: The price data in bytes, or an error. - fn get_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error>; + fn get_price_no_older_than( + &mut self, + id: B256, + age: U256, + ) -> Result, Self::Error>; /// Retrieves the exponentially-weighted moving average (EMA) price without recency checks. - /// + /// /// # Parameters /// - `id`: The unique identifier for the price feed. /// /// # Returns /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. - fn get_ema_price_unsafe(&mut self, id: B256) -> Result, Self::Error>; - + fn get_ema_price_unsafe( + &mut self, + id: B256, + ) -> Result, Self::Error>; + /// Retrieves an EMA price that is no older than the specified `age`. - /// + /// /// # Parameters /// - `id`: The unique identifier for the price feed. /// - `age`: The maximum acceptable age of the price in seconds. /// /// # Returns /// - `Result, Self::Error>`: The EMA price data in bytes, or an error. - fn get_ema_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error>; + fn get_ema_price_no_older_than( + &mut self, + id: B256, + age: U256, + ) -> Result, Self::Error>; /// Updates price feeds with the given data. /// @@ -65,7 +72,10 @@ pub trait IPyth { /// /// # Returns /// - `Result<(), Self::Error>`: Success or error. - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error>; + fn update_price_feeds( + &mut self, + update_data: Vec, + ) -> Result<(), Self::Error>; /// Updates price feeds if necessary, based on given publish times. /// @@ -90,9 +100,12 @@ pub trait IPyth { /// /// # Returns /// - `Result`: The required fee in Wei, or an error. - fn get_update_fee(&mut self, update_data: Vec) -> Result; + fn get_update_fee( + &mut self, + update_data: Vec, + ) -> Result; - /// Returns the fee required to update the price feeds based on the provided data. + /// Returns the fee required to update the price feeds based on the provided data. /// /// # Parameters /// - `update_data`: Array of price update data. @@ -150,31 +163,42 @@ sol_storage! { unsafe impl TopLevelStorage for PythContract {} #[public] -impl IPyth for PythContract { - +impl IPyth for PythContract { type Error = Vec; fn get_price_unsafe(&mut self, id: B256) -> Result, Self::Error> { let price = get_price_unsafe(self, self._ipyth.get(), id)?; - let data = price.abi_encode(); + let data = price.abi_encode(); Ok(data) } - - fn get_price_no_older_than(&mut self,id: B256, age: U256) -> Result, Self::Error> { - let price = get_price_no_older_than(self, self._ipyth.get(), id,age)?; - let data = price.abi_encode(); + + fn get_price_no_older_than( + &mut self, + id: B256, + age: U256, + ) -> Result, Self::Error> { + let price = get_price_no_older_than(self, self._ipyth.get(), id, age)?; + let data = price.abi_encode(); Ok(data) } - fn get_ema_price_unsafe(&mut self, id: B256) -> Result, Self::Error> { + fn get_ema_price_unsafe( + &mut self, + id: B256, + ) -> Result, Self::Error> { let price = get_ema_price_unsafe(self, self._ipyth.get(), id)?; - let data = price.abi_encode(); + let data = price.abi_encode(); Ok(data) } - fn get_ema_price_no_older_than(&mut self, id: B256, age: U256) -> Result, Self::Error> { - let price = get_ema_price_no_older_than(self, self._ipyth.get(), id,age)?; - let data = price.abi_encode(); + fn get_ema_price_no_older_than( + &mut self, + id: B256, + age: U256, + ) -> Result, Self::Error> { + let price = + get_ema_price_no_older_than(self, self._ipyth.get(), id, age)?; + let data = price.abi_encode(); Ok(data) } @@ -183,19 +207,25 @@ impl IPyth for PythContract { Ok(time) } - fn get_update_fee(&mut self, update_data: Vec) -> Result { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let fee = get_update_fee(self, self._ipyth.get(), data)?; - Ok(fee) + fn get_update_fee( + &mut self, + update_data: Vec, + ) -> Result { + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let fee = get_update_fee(self, self._ipyth.get(), data)?; + Ok(fee) } - + #[payable] - fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), Self::Error> { + fn update_price_feeds( + &mut self, + update_data: Vec, + ) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds(self, self._ipyth.get(),data)?; + update_price_feeds(self, self._ipyth.get(), data)?; Ok(()) } - + #[payable] fn update_price_feeds_if_necessary( &mut self, @@ -204,7 +234,13 @@ impl IPyth for PythContract { publish_times: Vec, ) -> Result<(), Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - update_price_feeds_if_necessary(self, self._ipyth.get(),data,price_ids,publish_times)?; + update_price_feeds_if_necessary( + self, + self._ipyth.get(), + data, + price_ids, + publish_times, + )?; Ok(()) } @@ -217,7 +253,15 @@ impl IPyth for PythContract { max_publish_time: u64, ) -> Result, Self::Error> { let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let encode_data= parse_price_feed_updates(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); + let encode_data = parse_price_feed_updates( + self, + self._ipyth.get(), + data, + price_ids, + min_publish_time, + max_publish_time, + )? + .abi_encode(); Ok(encode_data) } @@ -229,8 +273,16 @@ impl IPyth for PythContract { min_publish_time: u64, max_publish_time: u64, ) -> Result, Self::Error> { - let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); - let encode_data= parse_price_feed_updates_unique(self, self._ipyth.get(), data, price_ids, min_publish_time, max_publish_time)?.abi_encode(); - Ok(encode_data) + let data = update_data.into_iter().map(|x| Bytes::from(x.0)).collect(); + let encode_data = parse_price_feed_updates_unique( + self, + self._ipyth.get(), + data, + price_ids, + min_publish_time, + max_publish_time, + )? + .abi_encode(); + Ok(encode_data) } -} \ No newline at end of file +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 8eeccc7b6d..3b48109cf6 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -1,11 +1,9 @@ - #![allow(missing_docs)] //! Solidity type definitions used throughout the project -use alloy_primitives::{ I32, I64, U64, U256}; +use alloy_primitives::{I32, I64, U256, U64}; use stylus_sdk::{alloy_sol_types::sol, prelude::*}; - -sol_storage !{ +sol_storage! { /// Represents a storage-optimized price structure containing the current price data. pub struct StoragePrice { int64 price; @@ -13,7 +11,7 @@ sol_storage !{ int32 expo; uint publish_time; } - + /// Represents a storage-optimized price feed structure containing an ID and associated price data. pub struct StoragePriceFeed { bytes32 id; @@ -22,7 +20,6 @@ sol_storage !{ } } - sol! { /// Represents a price structure containing the current price data. /// @@ -55,10 +52,10 @@ sol! { Price ema_price; } /// Function call selector: Fetches the price associated with the given ID without validation. - /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. function getPriceUnsafe(bytes32 id) external view returns (int64,uint64,int32,uint); - /// Function call selector: Retrieves the price associated with the given ID + /// Function call selector: Retrieves the price associated with the given ID /// ensuring that the price is not older than the specified age in seconds. /// Returns the `Price` data for the specified `bytes32` ID. function getPriceNoOlderThan( @@ -66,14 +63,14 @@ sol! { uint age ) external view returns (Price memory price); - /// Function call selector: Fetches the Exponential Moving Average (EMA) price - /// associated with the given ID without validation. - /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. + /// Function call selector: Fetches the Exponential Moving Average (EMA) price + /// associated with the given ID without validation. + /// Returns the raw `Price` data for the specified `bytes32` ID, acting as a return call selector. function getEmaPriceUnsafe( bytes32 id ) external view returns (Price memory price); - /// Function call selector: Retrieves the EMA price associated with the given ID + /// Function call selector: Retrieves the EMA price associated with the given ID /// ensuring that the price is not older than the specified age in seconds. /// Returns the `Price` data for the specified `bytes32` ID. function getEmaPriceNoOlderThan( @@ -86,7 +83,7 @@ sol! { function updatePriceFeeds(bytes[] calldata updateData) external payable; /// Function call selector: Updates multiple price feeds only if necessary based on the provided conditions. - /// Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds + /// Accepts arrays of `bytes`, `bytes32`, and `uint64` to check against existing feeds /// and processes the updates if conditions are met. function updatePriceFeedsIfNecessary( bytes[] calldata updateData, @@ -135,10 +132,10 @@ sol! { /// Function call selector: Retrieves the valid time period for price feeds. /// Returns the valid time period as a `uint`. - function getValidTimePeriod() - public - view - virtual + function getValidTimePeriod() + public + view + virtual returns (uint validTimePeriod); } @@ -146,7 +143,7 @@ impl StoragePrice { /// Converts the `StoragePrice` instance into a `Price` struct. /// /// This method retrieves the stored values from the `StoragePrice` - /// and creates a `Price` struct, making it suitable for external + /// and creates a `Price` struct, making it suitable for external /// use or return types in function calls. pub fn to_price(&self) -> Price { Price { @@ -158,8 +155,8 @@ impl StoragePrice { } /// Sets the values of the `StoragePrice` instance from a given `Price` struct. - /// This method updates the stored values in the `StoragePrice` with - /// the corresponding values from the provided `Price` struct, ensuring + /// This method updates the stored values in the `StoragePrice` with + /// the corresponding values from the provided `Price` struct, ensuring /// that the internal state is accurately reflected. pub fn set(&mut self, price: Price) { self.price.set(I64::try_from(price.price).unwrap()); @@ -169,8 +166,9 @@ impl StoragePrice { } /// This function is for just for testing - pub fn from_price(price: Price) -> Self { - let mut storage_price = unsafe { StoragePrice::new(U256::from(100), 0)}; + pub fn from_price(price: Price) -> Self { + let mut storage_price = + unsafe { StoragePrice::new(U256::from(100), 0) }; storage_price.set(price); storage_price } @@ -179,9 +177,9 @@ impl StoragePrice { impl StoragePriceFeed { /// Converts the `StoragePriceFeed` instance into a `PriceFeed` struct. /// - /// This method retrieves the stored price feed values and creates - /// a `PriceFeed` struct, which includes the unique identifier and - /// associated price data, making it suitable for external use or + /// This method retrieves the stored price feed values and creates + /// a `PriceFeed` struct, which includes the unique identifier and + /// associated price data, making it suitable for external use or /// return types in function calls. pub fn to_price_feed(&self) -> PriceFeed { PriceFeed { @@ -193,8 +191,8 @@ impl StoragePriceFeed { /// Sets the values of the `StoragePriceFeed` instance from a given `PriceFeed` struct. /// - /// This method updates the stored values in the `StoragePriceFeed` - /// with the corresponding values from the provided `PriceFeed` struct, + /// This method updates the stored values in the `StoragePriceFeed` + /// with the corresponding values from the provided `PriceFeed` struct, /// ensuring that the internal state is accurately reflected. pub fn set(&mut self, price_feed: PriceFeed) { self.id.set(price_feed.id); @@ -202,27 +200,25 @@ impl StoragePriceFeed { self.ema_price.set(price_feed.ema_price); } - pub fn from_price_feed(price_feed: PriceFeed) -> Self { - let mut storage_price_feed = unsafe { StoragePriceFeed::new(U256::from(100), 0)}; + pub fn from_price_feed(price_feed: PriceFeed) -> Self { + let mut storage_price_feed = + unsafe { StoragePriceFeed::new(U256::from(100), 0) }; storage_price_feed.set(price_feed); storage_price_feed } - } - - #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::{ B256, U256}; - use crate::pyth::types::{PriceFeed, Price, StoragePriceFeed, StoragePrice}; + use crate::pyth::types::{ + Price, PriceFeed, StoragePrice, StoragePriceFeed, + }; + use alloy_primitives::{B256, U256}; // Updated constants to use uppercase naming convention const PRICE: i64 = 1000; const CONF: u64 = 1000; - const EXPO: i32 = 1000; - - + const EXPO: i32 = 1000; /// Generates a 256-bit hash filled with the byte value 30. /// @@ -234,19 +230,35 @@ mod tests { #[motsu::test] fn can_create_type_price() { - let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_result = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; assert_eq!(price_result.price, PRICE); assert_eq!(price_result.conf, CONF); assert_eq!(price_result.expo, EXPO); assert_eq!(price_result.publish_time, U256::from(1000)); } - #[motsu::test] + #[motsu::test] fn can_create_type_price_feed() { let id = generate_bytes(); - let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; - let price_result_ema = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; - let price_feed_result = PriceFeed{ id, price: price_result, ema_price: price_result_ema}; + let price_result = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; + let price_result_ema = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; + let price_feed_result = + PriceFeed { id, price: price_result, ema_price: price_result_ema }; assert_eq!(price_feed_result.price.price, PRICE); assert_eq!(price_feed_result.price.conf, CONF); assert_eq!(price_feed_result.price.expo, EXPO); @@ -259,7 +271,12 @@ mod tests { #[motsu::test] fn can_create_type_storage_price() { - let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; + let price_result = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; let storage_price_result = StoragePrice::from_price(price_result); let price_result = storage_price_result.to_price(); assert_eq!(price_result.price, PRICE); @@ -268,12 +285,27 @@ mod tests { assert_eq!(price_result.publish_time, U256::from(1000)); } - #[motsu::test] + #[motsu::test] fn can_create_type_storage_price_feed() { - let price_result = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; - let price_result_ema = Price{price: PRICE, conf: CONF, expo: EXPO, publish_time: U256::from(1000)}; - let price_feed_result = PriceFeed{ id: generate_bytes(), price: price_result, ema_price: price_result_ema}; - let storage_price_feed_result = StoragePriceFeed::from_price_feed(price_feed_result); + let price_result = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; + let price_result_ema = Price { + price: PRICE, + conf: CONF, + expo: EXPO, + publish_time: U256::from(1000), + }; + let price_feed_result = PriceFeed { + id: generate_bytes(), + price: price_result, + ema_price: price_result_ema, + }; + let storage_price_feed_result = + StoragePriceFeed::from_price_feed(price_feed_result); let price_feed_result = storage_price_feed_result.to_price_feed(); assert_eq!(price_feed_result.price.price, PRICE); assert_eq!(price_feed_result.price.conf, CONF); @@ -284,5 +316,4 @@ mod tests { assert_eq!(price_feed_result.ema_price.expo, EXPO); assert_eq!(price_feed_result.ema_price.publish_time, U256::from(1000)); } - -} \ No newline at end of file +} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs index cce537cbce..926fad11f9 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs @@ -1,25 +1,17 @@ use { alloc::vec::Vec, - alloy_sol_types::{ - SolCall, - SolType, - - }, + alloy_sol_types::{SolCall, SolType}, stylus_sdk::{ alloy_primitives::Address, - call::{ - call, - delegate_call, - static_call, - }, + call::{call, delegate_call, static_call}, storage::TopLevelStorage, }, }; - /// The revert message when failing to decode the data /// returned by an external contract call -pub const CALL_RETDATA_DECODING_ERROR_MESSAGE: &[u8] = b"error decoding retdata"; +pub const CALL_RETDATA_DECODING_ERROR_MESSAGE: &[u8] = + b"error decoding retdata"; /// Maps an error returned from an external contract call to a `Vec`, /// which is the expected return type of external contract methods. @@ -40,9 +32,10 @@ pub fn delegate_call_helper( address: Address, args: as SolType>::RustType, ) -> Result> { - let calldata = C::new(args).abi_encode(); - let res = unsafe { delegate_call(storage, address, &calldata).map_err(map_call_error)? }; + let res = unsafe { + delegate_call(storage, address, &calldata).map_err(map_call_error)? + }; C::abi_decode_returns(&res, false /* validate */) .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } @@ -55,7 +48,8 @@ pub fn static_call_helper( args: as SolType>::RustType, ) -> Result> { let calldata = C::new(args).abi_encode(); - let res = static_call(storage, address, &calldata).map_err(map_call_error)?; + let res = + static_call(storage, address, &calldata).map_err(map_call_error)?; C::abi_decode_returns(&res, false /* validate */) .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) } @@ -82,4 +76,4 @@ pub fn decode_helper(data: &[u8]) -> Result> { /// Encodes the data to be returned by an external contract call pub fn encode_helper(data: T::RustType) -> Vec { T::abi_encode(&data) -} \ No newline at end of file +} diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs index 9f423ceba4..093d047b29 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/src/lib.rs @@ -4,17 +4,21 @@ extern crate alloc; use alloc::vec; use alloc::vec::Vec; use alloy_primitives::U256; -use stylus_sdk::{console, prelude::{entrypoint,public, sol_storage, SolidityError}}; +use alloy_sol_types::sol; use pyth_stylus::pyth::{ - mock::create_price_feed_update_data_list, functions::{ - get_ema_price_no_older_than, get_ema_price_unsafe, get_price_no_older_than, get_price_unsafe, get_update_fee, get_valid_time_period, update_price_feeds, update_price_feeds_if_necessary - },types::{ - StoragePrice, - StoragePriceFeed -}}; -use alloy_sol_types::sol; - + get_ema_price_no_older_than, get_ema_price_unsafe, + get_price_no_older_than, get_price_unsafe, get_update_fee, + get_valid_time_period, update_price_feeds, + update_price_feeds_if_necessary, + }, + mock::create_price_feed_update_data_list, + types::{StoragePrice, StoragePriceFeed}, +}; +use stylus_sdk::{ + console, + prelude::{entrypoint, public, sol_storage, SolidityError}, +}; sol_storage! { #[entrypoint] @@ -28,7 +32,6 @@ sol_storage! { } } - sol! { error ArraySizeNotMatch(); @@ -39,58 +42,79 @@ sol! { #[derive(SolidityError)] pub enum MultiCallErrors { - ArraySizeNotMatch(ArraySizeNotMatch), CallFailed(CallFailed), - } - #[public] impl FunctionCallsExample { pub fn get_price_unsafe(&mut self) -> Result> { - let price_result = get_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; - self.price.set(price_result); - Ok(price_result.price) + let price_result = get_price_unsafe( + self, + self.pyth_address.get(), + self.price_id.get(), + )?; + self.price.set(price_result); + Ok(price_result.price) } pub fn get_ema_price_unsafe(&mut self) -> Result> { - let price_result = get_ema_price_unsafe(self, self.pyth_address.get(), self.price_id.get())?; - Ok(price_result.price) + let price_result = get_ema_price_unsafe( + self, + self.pyth_address.get(), + self.price_id.get(), + )?; + Ok(price_result.price) } pub fn get_price_no_older_than(&mut self) -> Result> { - let price_result = get_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; - Ok(price_result.price) + let price_result = get_price_no_older_than( + self, + self.pyth_address.get(), + self.price_id.get(), + U256::from(1000), + )?; + Ok(price_result.price) } pub fn get_ema_price_no_older_than(&mut self) -> Result> { - let price_result = get_ema_price_no_older_than(self, self.pyth_address.get(), self.price_id.get(), U256::from(1000))?; - Ok(price_result.price) + let price_result = get_ema_price_no_older_than( + self, + self.pyth_address.get(), + self.price_id.get(), + U256::from(1000), + )?; + Ok(price_result.price) } pub fn get_update_fee(&mut self) -> Result> { - let (data,_) = create_price_feed_update_data_list(); - let fee = get_update_fee(self, self.pyth_address.get(), data)?; + let (data, _) = create_price_feed_update_data_list(); + let fee = get_update_fee(self, self.pyth_address.get(), data)?; Ok(fee) } pub fn get_valid_time_period(&mut self) -> Result> { - let time = get_valid_time_period(self, self.pyth_address.get())?; - Ok(time) + let time = get_valid_time_period(self, self.pyth_address.get())?; + Ok(time) } #[payable] pub fn update_price_feeds(&mut self) -> Result<(), Vec> { - let (data_bytes, _) = create_price_feed_update_data_list(); - let _ = update_price_feeds(self, self.pyth_address.get(), data_bytes)?; + let (data_bytes, _) = create_price_feed_update_data_list(); + let _ = update_price_feeds(self, self.pyth_address.get(), data_bytes)?; Ok(()) } #[payable] pub fn update_price_feeds_if_necessary(&mut self) -> Result<(), Vec> { - let (data,ids) = create_price_feed_update_data_list(); - let _ = update_price_feeds_if_necessary(self, self.pyth_address.get(), data, ids,vec![0, 0, 0])?; - Ok(()) + let (data, ids) = create_price_feed_update_data_list(); + let _ = update_price_feeds_if_necessary( + self, + self.pyth_address.get(), + data, + ids, + vec![0, 0, 0], + )?; + Ok(()) } } diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs index e98ebffd9d..d7d4fcb067 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/abi/mod.rs @@ -11,6 +11,6 @@ sol!( function getUpdateFee() external returns (uint256 fee); function getValidTimePeriod() external returns (uint256 period); function updatePriceFeeds() external payable; - function updatePriceFeedsIfNecessary() external payable; + function updatePriceFeedsIfNecessary() external payable; } -); \ No newline at end of file +); diff --git a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs index 930504b5a8..4ccdc16e8c 100644 --- a/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/function-calls/tests/function-calls.rs @@ -3,42 +3,36 @@ use std::assert_eq; use abi::FunctionCalls; -use alloy::{ - primitives:: U256, - sol, -}; -use alloy_primitives::{ Address, FixedBytes}; -use e2e::{ Account, ReceiptExt,env -}; +use alloy::{primitives::U256, sol}; +use alloy_primitives::{Address, FixedBytes}; +use e2e::{env, Account, ReceiptExt}; -use eyre::Result; use crate::FunctionCallsExample::constructorCall; +use eyre::Result; mod abi; sol!("src/constructor.sol"); - impl Default for constructorCall { fn default() -> Self { ctr("ETH", false) } } - fn ctr(key_id: &str, invaid_pyth_address: bool) -> constructorCall { - let pyth_addr = if invaid_pyth_address { + let pyth_addr = if invaid_pyth_address { "0xdC79c650B6560cBF15391C3F90A833a349735676".to_string() } else { env("MOCK_PYTH_ADDRESS").unwrap() }; let address_addr = Address::parse_checksummed(&pyth_addr, None).unwrap(); - let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); + let id = keccak_const::Keccak256::new() + .update(key_id.as_bytes()) + .finalize() + .to_vec(); let price_id = FixedBytes::<32>::from_slice(&id); - constructorCall { - _pythAddress: address_addr, - _priceId: price_id - } + constructorCall { _pythAddress: address_addr, _priceId: price_id } } #[e2e::test] @@ -46,7 +40,7 @@ async fn constructs(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; @@ -58,7 +52,7 @@ async fn constructs(alice: Account) -> Result<()> { async fn error_provided_invaild_id(alice: Account) -> Result<()> { let contract_addr = alice .as_deployer() - .with_constructor(ctr("BALLON",false)) + .with_constructor(ctr("BALLON", false)) .deploy() .await? .address()?; @@ -87,12 +81,13 @@ async fn can_get_price_unsafe(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe().call().await?; + let FunctionCalls::getPriceUnsafeReturn { price } = + contract.getPriceUnsafe().call().await?; assert!(price > 0); Ok(()) } @@ -102,28 +97,29 @@ async fn can_get_ema_price_unsafe(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getEmaPriceUnsafeReturn { price } = contract.getEmaPriceUnsafe().call().await?; + let FunctionCalls::getEmaPriceUnsafeReturn { price } = + contract.getEmaPriceUnsafe().call().await?; assert!(price > 0); Ok(()) } - #[e2e::test] async fn can_get_price_no_older_than(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getPriceNoOlderThanReturn { price } = contract.getPriceNoOlderThan().call().await?; + let FunctionCalls::getPriceNoOlderThanReturn { price } = + contract.getPriceNoOlderThan().call().await?; assert!(price > 0); Ok(()) } @@ -133,12 +129,13 @@ async fn can_get_ema_price_no_older_than(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getEmaPriceNoOlderThanReturn { price } = contract.getEmaPriceNoOlderThan().call().await?; + let FunctionCalls::getEmaPriceNoOlderThanReturn { price } = + contract.getEmaPriceNoOlderThan().call().await?; assert!(price > 0); Ok(()) } @@ -148,12 +145,13 @@ async fn can_get_fee(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getUpdateFeeReturn { fee } = contract.getUpdateFee().call().await?; + let FunctionCalls::getUpdateFeeReturn { fee } = + contract.getUpdateFee().call().await?; assert_eq!(fee, U256::from(300)); Ok(()) } @@ -163,12 +161,13 @@ async fn can_get_period(alice: Account) -> Result<()> { // Deploy contract using `alice` account let contract_addr = alice .as_deployer() - .with_default_constructor::() // Assuming `constructorCall` is the constructor here + .with_default_constructor::() // Assuming `constructorCall` is the constructor here .deploy() .await? .address()?; let contract = FunctionCalls::new(contract_addr, &alice.wallet); - let FunctionCalls::getValidTimePeriodReturn { period } = contract.getValidTimePeriod().call().await?; + let FunctionCalls::getValidTimePeriodReturn { period } = + contract.getValidTimePeriod().call().await?; assert_eq!(period, U256::from(100000)); Ok(()) } diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs index 90f1c1624e..f0c7d7a4f4 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/src/lib.rs @@ -1,9 +1,8 @@ #![cfg_attr(not(test), no_std, no_main)] extern crate alloc; -use stylus_sdk::prelude::{entrypoint,public, sol_storage,}; use pyth_stylus::pyth::pyth_contract::PythContract; - +use stylus_sdk::prelude::{entrypoint, public, sol_storage}; sol_storage! { #[entrypoint] @@ -15,5 +14,4 @@ sol_storage! { #[public] #[inherit(PythContract)] -impl ProxyCallsExample { -} \ No newline at end of file +impl ProxyCallsExample {} diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs index fae79db057..e6d6f54107 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/abi/mod.rs @@ -13,4 +13,4 @@ sol!( function updatePriceFeeds(bytes[] calldata updateData) external payable; function updatePriceFeedsIfNecessary(bytes[] calldata updateData, bytes32[] calldata priceIds, uint64[] calldata publishTimes) external payable; } -); \ No newline at end of file +); diff --git a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs index 5bb4b22e5b..3fa6d9218e 100644 --- a/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs +++ b/target_chains/ethereum/sdk/stylus/examples/proxy-calls/tests/proxy-calls.rs @@ -3,26 +3,22 @@ use std::assert_eq; use abi::ProxyCalls; -use alloy::{ - primitives:: U256, - sol, -}; +use alloy::{primitives::U256, sol}; use alloy_primitives::{Address, FixedBytes}; -use e2e::{ - Account, ReceiptExt,env -}; use alloy_sol_types::SolValue; +use e2e::{env, Account, ReceiptExt}; -use eyre::Result; -use pyth_stylus::pyth::{mock::create_price_feed_update_data_list, types::Price}; use crate::ProxyCallsExample::constructorCall; +use eyre::Result; +use pyth_stylus::pyth::{ + mock::create_price_feed_update_data_list, types::Price, +}; mod abi; - sol!("src/constructor.sol"); // // ============================================================================ -// // Integration Tests: Proxy Calls +// // Integration Tests: Proxy Calls // // ============================================================================ impl Default for constructorCall { @@ -32,184 +28,199 @@ impl Default for constructorCall { } fn generate_pyth_id_from_str(key_id: &str) -> FixedBytes<32> { - let id = keccak_const::Keccak256::new().update(key_id.as_bytes()).finalize().to_vec(); + let id = keccak_const::Keccak256::new() + .update(key_id.as_bytes()) + .finalize() + .to_vec(); let price_id = FixedBytes::<32>::from_slice(&id); price_id } fn ctr(invaid_pyth_address: bool) -> constructorCall { - let pyth_addr = if invaid_pyth_address { + let pyth_addr = if invaid_pyth_address { "0xdC79c650B6560cBF15391C3F90A833a349735676".to_string() } else { env("MOCK_PYTH_ADDRESS").unwrap() }; let address_addr = Address::parse_checksummed(&pyth_addr, None).unwrap(); - constructorCall { - _pythAddress: address_addr - } + constructorCall { _pythAddress: address_addr } } #[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - let contract_addr = alice +async fn constructs(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; - + let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("ETH"); - contract.getPriceUnsafe(id).call().await?; + contract.getPriceUnsafe(id).call().await?; Ok(()) - } - +} #[e2e::test] -async fn can_get_price_unsafe(alice: Account) -> Result<()> { - let contract_addr = alice +async fn can_get_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("ETH"); - let ProxyCalls::getPriceUnsafeReturn { price } = contract.getPriceUnsafe(id).call().await?; - let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + let ProxyCalls::getPriceUnsafeReturn { price } = + contract.getPriceUnsafe(id).call().await?; + let decoded_price = + Price::abi_decode(&price, false).expect("Failed to decode price"); assert!(decoded_price.price > 0_i64); - assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.conf > 0_u64); assert!(decoded_price.expo > 0_i32); - assert!(decoded_price.publish_time > U256::from(0)); + assert!(decoded_price.publish_time > U256::from(0)); Ok(()) - } +} #[e2e::test] -async fn error_provided_invaild_id_get_price_unsafe(alice: Account) -> Result<()> { - let contract_addr = alice +async fn error_provided_invaild_id_get_price_unsafe( + alice: Account, +) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("BALLON"); - let price_result = contract.getPriceUnsafe(id).call().await; + let price_result = contract.getPriceUnsafe(id).call().await; assert!(price_result.is_err()); Ok(()) - } - +} #[e2e::test] -async fn can_get_ema_price_unsafe(alice: Account) -> Result<()> { - let contract_addr = alice +async fn can_get_ema_price_unsafe(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("ETH"); - let ProxyCalls::getEmaPriceUnsafeReturn { price } = contract.getEmaPriceUnsafe(id).call().await?; - let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + let ProxyCalls::getEmaPriceUnsafeReturn { price } = + contract.getEmaPriceUnsafe(id).call().await?; + let decoded_price = + Price::abi_decode(&price, false).expect("Failed to decode price"); assert!(decoded_price.price > 0_i64); - assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.conf > 0_u64); assert!(decoded_price.expo > 0_i32); - assert!(decoded_price.publish_time > U256::from(0)); + assert!(decoded_price.publish_time > U256::from(0)); Ok(()) - } - +} - #[e2e::test] -async fn error_provided_invaild_id_get_ema_price_unsafe(alice: Account) -> Result<()> { - let contract_addr = alice +#[e2e::test] +async fn error_provided_invaild_id_get_ema_price_unsafe( + alice: Account, +) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("BALLON"); - let price_result = contract.getEmaPriceUnsafe(id).call().await; + let price_result = contract.getEmaPriceUnsafe(id).call().await; assert!(price_result.is_err()); Ok(()) - } +} #[e2e::test] -async fn can_get_price_no_older_than(alice: Account) -> Result<()> { - let contract_addr = alice +async fn can_get_price_no_older_than(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("ETH"); - let ProxyCalls::getPriceNoOlderThanReturn { price } = contract.getPriceNoOlderThan(id, U256::from(1000)).call().await?; - let decoded_price = Price::abi_decode(&price, false).expect("Failed to decode price"); + let ProxyCalls::getPriceNoOlderThanReturn { price } = + contract.getPriceNoOlderThan(id, U256::from(1000)).call().await?; + let decoded_price = + Price::abi_decode(&price, false).expect("Failed to decode price"); assert!(decoded_price.price > 0_i64); - assert!(decoded_price.conf > 0_u64); + assert!(decoded_price.conf > 0_u64); assert!(decoded_price.expo > 0_i32); - assert!(decoded_price.publish_time > U256::from(0)); + assert!(decoded_price.publish_time > U256::from(0)); Ok(()) - } +} #[e2e::test] -async fn error_provided_invaild_id_get_price_no_older_than(alice: Account) -> Result<()> { - let contract_addr = alice +async fn error_provided_invaild_id_get_price_no_older_than( + alice: Account, +) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("BALLON"); - let price_result = contract.getPriceNoOlderThan(id, U256::from(1000)).call().await; + let price_result = + contract.getPriceNoOlderThan(id, U256::from(1000)).call().await; assert!(price_result.is_err()); Ok(()) - } +} - #[e2e::test] -async fn error_provided_invaild_period_get_price_no_older_than(alice: Account) -> Result<()> { - let contract_addr = alice +#[e2e::test] +async fn error_provided_invaild_period_get_price_no_older_than( + alice: Account, +) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); let id = generate_pyth_id_from_str("SOL"); - let price_result = contract.getPriceNoOlderThan(id, U256::from(1)).call().await; + let price_result = + contract.getPriceNoOlderThan(id, U256::from(1)).call().await; assert!(price_result.is_err()); Ok(()) - } +} - #[e2e::test] -async fn can_get_fee(alice: Account) -> Result<()> { - let contract_addr = alice +#[e2e::test] +async fn can_get_fee(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); - let (data , _id) = create_price_feed_update_data_list(); - let ProxyCalls::getUpdateFeeReturn { fee } = contract.getUpdateFee(data).call().await?; - assert_eq!(fee , U256::from(300)); + let (data, _id) = create_price_feed_update_data_list(); + let ProxyCalls::getUpdateFeeReturn { fee } = + contract.getUpdateFee(data).call().await?; + assert_eq!(fee, U256::from(300)); Ok(()) - } +} - #[e2e::test] - async fn can_get_valid_time_peroid(alice: Account) -> Result<()> { - let contract_addr = alice +#[e2e::test] +async fn can_get_valid_time_peroid(alice: Account) -> Result<()> { + let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_default_constructor::() .deploy() .await? .address()?; let contract = ProxyCalls::new(contract_addr, &alice.wallet); - let ProxyCalls::getValidTimePeriodReturn { period } = contract.getValidTimePeriod().call().await?; - assert_eq!(period , U256::from(100000)); + let ProxyCalls::getValidTimePeriodReturn { period } = + contract.getValidTimePeriod().call().await?; + assert_eq!(period, U256::from(100000)); Ok(()) - } - \ No newline at end of file +} From 8617b13c5b2da5ee059cb0a1ee3c80b5610900c2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 17 Nov 2024 23:14:58 +0000 Subject: [PATCH 120/160] chore: fixed scripts --- .../sdk/stylus/benches/src/proxy_calls.rs | 19 +++++------ .../pyth-solidity/script/MockPyth.s.sol | 2 -- .../ethereum/sdk/stylus/scripts/bench.sh | 32 +++++++++---------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 281d0ddad0..0dda002530 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -63,7 +63,7 @@ pub async fn run_with( let time_frame = uint!(10000_U256); let age = uint!(10000_U256); - let (data, ids) = create_price_feed_update_data_list(); + let (data, _ids) = create_price_feed_update_data_list(); let _ = receipt!(contract.getPriceUnsafe(id))?; let _ = receipt!(contract.getEmaPriceUnsafe(id))?; @@ -73,15 +73,16 @@ pub async fn run_with( let _ = receipt!(contract.getUpdateFee(data.clone()))?; let _ = receipt!(contract.updatePriceFeeds(data.clone()))?; + // I will add updatePriceFeedsIfNecessary benchmark when it's ready //println!("{data:?} , {ids:?} "); - let _ = contract - .updatePriceFeedsIfNecessary( - data.clone(), - ids.clone(), - vec![100, 100, 100], - ) - .send() - .await?; + // let _ = contract + // .updatePriceFeedsIfNecessary( + // data.clone(), + // ids.clone(), + // vec![100, 100, 100], + // ) + // .send() + // .await?; // IMPORTANT: Order matters! use ProxyCall::*; diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index e52867eaf2..421af8c09e 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -40,8 +40,6 @@ contract MockPythScript is Script { int32 expo = int32(uint32(_deriveRandom(randomBase, 3) % 40)); int64 emaPrice = int64(uint64(_deriveRandom(randomBase, 4) % 10)); uint64 emaConf = uint64(_deriveRandom(randomBase, 5) % 10); - string memory str = string(abi.encodePacked("block.timestamp :",uint256(block.timestamp))); - console.log(str); priceData[i] = pyth_contract.createPriceFeedUpdateData( priceIds[i], price, diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 4627143eb4..f7ea7bbca2 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -14,27 +14,25 @@ cd "nitro-testnode" cd .. cd "pyth-solidity" -# deployed_to=$( +deployed_to=$( forge script ./script/MockPyth.s.sol:MockPythScript \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ - --broadcast - # \ - # | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 -#) + --broadcast \ | grep -oP '(?<=Pyth contract address: )0x[a-fA-F0-9]{40}' | tail -n 1 +) -# export MOCK_PYTH_ADDRESS=$deployed_to -# cd .. -# # Output the captured address +export MOCK_PYTH_ADDRESS=$deployed_to +cd .. +# Output the captured address -# NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} -# cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} +cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort -# # No need to compile benchmarks with `--release` -# # since this only runs the benchmarking code and the contracts have already been compiled with `--release` -# cargo run -p benches +# No need to compile benchmarks with `--release` +# since this only runs the benchmarking code and the contracts have already been compiled with `--release` +cargo run -p benches -# echo "NOTE: To measure non cached contract's gas usage correctly, -# benchmarks should run on a clean instance of the nitro test node." -# echo -# echo "Finished running benches!" +echo "NOTE: To measure non cached contract's gas usage correctly, + benchmarks should run on a clean instance of the nitro test node." +echo +echo "Finished running benches!" From 746dfdf2015db2b9effd09645b400f771c144b57 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 15:20:15 +0000 Subject: [PATCH 121/160] Update target_chains/ethereum/sdk/stylus/README.md Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index aebd52301a..44026c9482 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -17,7 +17,7 @@ To add the Stylus Contracts from crates.io, add the following line to your `Carg ```toml [dependencies] -pyth-stylus = "0.0.1" +pyth-stylus = "0.1.0" ``` For the latest changes from the `main` branch, you can also specify a git dependency: From fa25f0600fc136ba719e083914a541a7a26f84bf Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 15:32:28 +0000 Subject: [PATCH 122/160] Update target_chains/ethereum/sdk/stylus/README.md Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 44026c9482..1d5708bb43 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -70,7 +70,7 @@ sol_storage! { #[entrypoint] struct ProxyCallsExample { #[borrow] - PythContract pyth + PythContract pyth; } } From fb942d2373753f40a1b11dd87942ea4cf4563054 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 15:35:18 +0000 Subject: [PATCH 123/160] chore: rustfmt changes --- target_chains/ethereum/sdk/stylus/rustfmt.toml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/rustfmt.toml diff --git a/target_chains/ethereum/sdk/stylus/rustfmt.toml b/target_chains/ethereum/sdk/stylus/rustfmt.toml deleted file mode 100644 index 769661dee7..0000000000 --- a/target_chains/ethereum/sdk/stylus/rustfmt.toml +++ /dev/null @@ -1,8 +0,0 @@ -max_width = 80 -format_macro_matchers = true -group_imports = "StdExternalCrate" -imports_granularity = "Crate" -reorder_impl_items = true -use_field_init_shorthand = true -use_small_heuristics = "Max" -wrap_comments = true From c2e06290b3d8f4c31329b841b559b3295422184e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 15:36:51 +0000 Subject: [PATCH 124/160] chore: changed solidity licenses to Apache 2 --- .../ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol | 2 +- .../ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol | 2 +- .../ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol index 421af8c09e..bb2de23c4f 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol @@ -1,4 +1,4 @@ -/// SPDX-License-Identifier: UNLICENSED +/// SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol index e6511b5348..3b6632a8cf 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.13; import "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol index f76d38b6a3..85c47c5ffd 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; From cf0f807159f50671df828012ca07f5c8e416f5ee Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 15:37:23 +0000 Subject: [PATCH 125/160] chore: deleted codeowner file and link --- target_chains/ethereum/sdk/stylus/.github/CODEOWNERS | 1 - target_chains/ethereum/sdk/stylus/Cargo.toml | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/.github/CODEOWNERS diff --git a/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS b/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS deleted file mode 100644 index 4f5d950206..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @Ifechukwudaniel diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 38c595f336..ba40f7a615 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -20,7 +20,7 @@ resolver = "2" authors = ["Ifechukwu Daniel"] edition = "2021" license = "MIT" -repository = "https://github.com/pyth-network/" +repository = "https://github.com/pyth-network/pyth-crosschain" version = "0.1.0" [workspace.lints.rust] @@ -28,9 +28,6 @@ missing_docs = "warn" unreachable_pub = "warn" rust_2021_compatibility = { level = "warn", priority = -1 } -[workspace.lints.clippy] -pedantic = "warn" -all = "warn" [workspace.dependencies] # stylus-related From 1b1e3512a3a9378020b76cfbe65a2191c1377089 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 15:39:42 +0000 Subject: [PATCH 126/160] chore: removed comments --- target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml b/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml index 25b918f9c9..2bc70ba516 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml +++ b/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml @@ -2,5 +2,3 @@ src = "src" out = "out" libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options From 7c529fe5e132487c9f78de502b29a472b777ab59 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:40:53 +0000 Subject: [PATCH 127/160] solidity changes --- .../.github/workflows/test.yml | 45 ++++++++++ .../sdk/stylus/pyth-mock-solidity/.gitignore | 15 ++++ .../sdk/stylus/pyth-mock-solidity/README.md | 67 +++++++++++++++ .../stylus/pyth-mock-solidity/foundry.toml | 4 + .../stylus/pyth-mock-solidity/lib/forge-std | 1 + .../lib/openzeppelin-contracts | 1 + .../lib/openzeppelin-foundry-upgrades | 1 + .../pyth-mock-solidity/package-lock.json | 28 +++++++ .../stylus/pyth-mock-solidity/package.json | 19 +++++ .../stylus/pyth-mock-solidity/remappings.txt | 1 + .../pyth-mock-solidity/script/MockPyth.s.sol | 83 +++++++++++++++++++ .../pyth-mock-solidity/src/MockPyth.sol | 13 +++ .../pyth-mock-solidity/test/MockPyth.t.sol | 9 ++ 13 files changed, 287 insertions(+) create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.gitignore create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/README.md create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/foundry.toml create mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std create mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts create mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/remappings.txt create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/script/MockPyth.s.sol create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/src/MockPyth.sol create mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/test/MockPyth.t.sol diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml new file mode 100644 index 0000000000..762a2966f7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.gitignore b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.gitignore new file mode 100644 index 0000000000..bb98a38c47 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.gitignore @@ -0,0 +1,15 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/* +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/README.md b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/README.md new file mode 100644 index 0000000000..a67dd3fc88 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/README.md @@ -0,0 +1,67 @@ +## Foundry + +**The goal of the folder is to deploy a mock solidity pyth contract using the pyth sdk** + + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat, and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions, and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/foundry.toml b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/foundry.toml new file mode 100644 index 0000000000..2bc70ba516 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/foundry.toml @@ -0,0 +1,4 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std new file mode 160000 index 0000000000..1eea5bae12 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts new file mode 160000 index 0000000000..dc44c9f1a4 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000000..16e0ae21e0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json new file mode 100644 index 0000000000..213e39f20b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "pyth-solidity", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "pyth-solidity", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } + }, + "node_modules/@pythnetwork/pyth-sdk-solidity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", + "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" + } + }, + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", + "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" + } + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json new file mode 100644 index 0000000000..372dfd8609 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json @@ -0,0 +1,19 @@ +{ + "name": "pyth-solidity", + "version": "1.0.0", + "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", + "main": "index.js", + "directories": { + "lib": "lib", + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@pythnetwork/pyth-sdk-solidity": "^4.0.0" + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/remappings.txt b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/remappings.txt new file mode 100644 index 0000000000..939d22cd46 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/remappings.txt @@ -0,0 +1 @@ +@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/script/MockPyth.s.sol new file mode 100644 index 0000000000..bb2de23c4f --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/script/MockPyth.s.sol @@ -0,0 +1,83 @@ +/// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {MockPythSample} from "../src/MockPyth.sol"; + +contract MockPythScript is Script { + uint randNonce = 0; + string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; + bytes[] priceData = new bytes[](tickers.length); + bytes32[] priceIds = new bytes32[](tickers.length); + MockPythSample pyth_contract; + + function setUp() public { + + } + + function run() public { + vm.startBroadcast(); + deploy(100000, 100); + _generateInitialData(); + console.log("Pyth contract address:", address(pyth_contract)); + uint update_cost = pyth_contract.getUpdateFee(priceData); + pyth_contract.updatePriceFeeds{value: update_cost}(priceData); + vm.stopBroadcast(); + } + + function deploy(uint validTimePeriod, uint singleUpdateFeeInWei) internal { + pyth_contract = new MockPythSample(validTimePeriod, singleUpdateFeeInWei); + } + + function _generateInitialData() internal { + for (uint i = 0; i < tickers.length; i++) { + priceIds[i] = keccak256(abi.encodePacked(tickers[i])); + + uint randomBase = uint(keccak256(abi.encodePacked(block.timestamp, priceIds[i], msg.sender))); + + int64 price = int64(uint64(_deriveRandom(randomBase, 1) % 10 )); + uint64 conf = uint64(_deriveRandom(randomBase, 2) % 10); + int32 expo = int32(uint32(_deriveRandom(randomBase, 3) % 40)); + int64 emaPrice = int64(uint64(_deriveRandom(randomBase, 4) % 10)); + uint64 emaConf = uint64(_deriveRandom(randomBase, 5) % 10); + priceData[i] = pyth_contract.createPriceFeedUpdateData( + priceIds[i], + price, + conf, + expo, + emaPrice, + emaConf, + uint64(block.timestamp), + 0 + ); + } + + } + + // Internal function to generate deterministic random numbers from a base seed + function _deriveRandom(uint base, uint salt) internal pure returns (uint) { + return uint(keccak256(abi.encodePacked(base, salt)) ) + 1; + } + + function uint2str(uint256 _i) internal pure returns (string memory str) { + if (_i == 0) { + return "0"; + } + uint256 j = _i; + uint256 length; + while (j != 0) + { + length++; + j /= 10; + } + bytes memory bstr = new bytes(length); + uint256 k = length; + j = _i; + while (j != 0) + { + bstr[--k] = bytes1(uint8(48 + j % 10)); + j /= 10; + } + str = string(bstr); + } +} diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/src/MockPyth.sol new file mode 100644 index 0000000000..3b6632a8cf --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/src/MockPyth.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.13; + +import "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; +import {console} from "forge-std/console.sol"; + +contract MockPythSample is MockPyth { + constructor( + uint validTimePeriod, uint singleUpdateFeeInWei + ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { + + } +} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/test/MockPyth.t.sol b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/test/MockPyth.t.sol new file mode 100644 index 0000000000..85c47c5ffd --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/test/MockPyth.t.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; + +contract MockPythTest is Test { + function setUp() public { + } +} From c1e656d37a28817c226815e5c9430409501668b3 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:51:30 +0000 Subject: [PATCH 128/160] chore: removed folder --- .../pyth-solidity/.github/workflows/test.yml | 45 ---------- .../sdk/stylus/pyth-solidity/.gitignore | 15 ---- .../sdk/stylus/pyth-solidity/README.md | 67 --------------- .../sdk/stylus/pyth-solidity/foundry.toml | 4 - .../sdk/stylus/pyth-solidity/lib/forge-std | 1 - .../pyth-solidity/lib/openzeppelin-contracts | 1 - .../lib/openzeppelin-foundry-upgrades | 1 - .../stylus/pyth-solidity/package-lock.json | 28 ------- .../sdk/stylus/pyth-solidity/package.json | 19 ----- .../sdk/stylus/pyth-solidity/remappings.txt | 1 - .../pyth-solidity/script/MockPyth.s.sol | 83 ------------------- .../sdk/stylus/pyth-solidity/src/MockPyth.sol | 13 --- .../stylus/pyth-solidity/test/MockPyth.t.sol | 9 -- 13 files changed, 287 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/README.md delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/package.json delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml deleted file mode 100644 index 762a2966f7..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/.github/workflows/test.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: CI - -on: - push: - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge fmt - run: | - forge fmt --check - id: fmt - - - name: Run Forge build - run: | - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore b/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore deleted file mode 100644 index bb98a38c47..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/* -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -# Docs -docs/ - -# Dotenv file -.env diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md b/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md deleted file mode 100644 index a67dd3fc88..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/README.md +++ /dev/null @@ -1,67 +0,0 @@ -## Foundry - -**The goal of the folder is to deploy a mock solidity pyth contract using the pyth sdk** - - -Foundry consists of: - -- **Forge**: Ethereum testing framework (like Truffle, Hardhat, and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions, and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. - -## Documentation - -https://book.getfoundry.sh/ - -## Usage - -### Build - -```shell -$ forge build -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml b/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml deleted file mode 100644 index 2bc70ba516..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/foundry.toml +++ /dev/null @@ -1,4 +0,0 @@ -[profile.default] -src = "src" -out = "out" -libs = ["lib"] diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std deleted file mode 160000 index 1eea5bae12..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts deleted file mode 160000 index dc44c9f1a4..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades b/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades deleted file mode 160000 index 16e0ae21e0..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/lib/openzeppelin-foundry-upgrades +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json b/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json deleted file mode 100644 index 213e39f20b..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/package-lock.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "pyth-solidity", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "pyth-solidity", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@pythnetwork/pyth-sdk-solidity": "^4.0.0" - } - }, - "node_modules/@pythnetwork/pyth-sdk-solidity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", - "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" - } - }, - "dependencies": { - "@pythnetwork/pyth-sdk-solidity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", - "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" - } - } -} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json deleted file mode 100644 index 372dfd8609..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "pyth-solidity", - "version": "1.0.0", - "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", - "main": "index.js", - "directories": { - "lib": "lib", - "test": "test" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@pythnetwork/pyth-sdk-solidity": "^4.0.0" - } -} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt b/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt deleted file mode 100644 index 939d22cd46..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/remappings.txt +++ /dev/null @@ -1 +0,0 @@ -@pythnetwork/pyth-sdk-solidity/=node_modules/@pythnetwork/pyth-sdk-solidity \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol deleted file mode 100644 index bb2de23c4f..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/script/MockPyth.s.sol +++ /dev/null @@ -1,83 +0,0 @@ -/// SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {MockPythSample} from "../src/MockPyth.sol"; - -contract MockPythScript is Script { - uint randNonce = 0; - string[] tickers = ["ETH", "BTC", "USDT", "BNB", "SOL", "MATIC", "ADA", "DOT", "DOGE", "SHIB"]; - bytes[] priceData = new bytes[](tickers.length); - bytes32[] priceIds = new bytes32[](tickers.length); - MockPythSample pyth_contract; - - function setUp() public { - - } - - function run() public { - vm.startBroadcast(); - deploy(100000, 100); - _generateInitialData(); - console.log("Pyth contract address:", address(pyth_contract)); - uint update_cost = pyth_contract.getUpdateFee(priceData); - pyth_contract.updatePriceFeeds{value: update_cost}(priceData); - vm.stopBroadcast(); - } - - function deploy(uint validTimePeriod, uint singleUpdateFeeInWei) internal { - pyth_contract = new MockPythSample(validTimePeriod, singleUpdateFeeInWei); - } - - function _generateInitialData() internal { - for (uint i = 0; i < tickers.length; i++) { - priceIds[i] = keccak256(abi.encodePacked(tickers[i])); - - uint randomBase = uint(keccak256(abi.encodePacked(block.timestamp, priceIds[i], msg.sender))); - - int64 price = int64(uint64(_deriveRandom(randomBase, 1) % 10 )); - uint64 conf = uint64(_deriveRandom(randomBase, 2) % 10); - int32 expo = int32(uint32(_deriveRandom(randomBase, 3) % 40)); - int64 emaPrice = int64(uint64(_deriveRandom(randomBase, 4) % 10)); - uint64 emaConf = uint64(_deriveRandom(randomBase, 5) % 10); - priceData[i] = pyth_contract.createPriceFeedUpdateData( - priceIds[i], - price, - conf, - expo, - emaPrice, - emaConf, - uint64(block.timestamp), - 0 - ); - } - - } - - // Internal function to generate deterministic random numbers from a base seed - function _deriveRandom(uint base, uint salt) internal pure returns (uint) { - return uint(keccak256(abi.encodePacked(base, salt)) ) + 1; - } - - function uint2str(uint256 _i) internal pure returns (string memory str) { - if (_i == 0) { - return "0"; - } - uint256 j = _i; - uint256 length; - while (j != 0) - { - length++; - j /= 10; - } - bytes memory bstr = new bytes(length); - uint256 k = length; - j = _i; - while (j != 0) - { - bstr[--k] = bytes1(uint8(48 + j % 10)); - j /= 10; - } - str = string(bstr); - } -} diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol deleted file mode 100644 index 3b6632a8cf..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/src/MockPyth.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.13; - -import "@pythnetwork/pyth-sdk-solidity/MockPyth.sol"; -import {console} from "forge-std/console.sol"; - -contract MockPythSample is MockPyth { - constructor( - uint validTimePeriod, uint singleUpdateFeeInWei - ) MockPyth(validTimePeriod, singleUpdateFeeInWei) { - - } -} \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol b/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol deleted file mode 100644 index 85c47c5ffd..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-solidity/test/MockPyth.t.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; - -contract MockPythTest is Test { - function setUp() public { - } -} From c9be56a5066b5e933f779f4424428b2403a9771e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:52:45 +0000 Subject: [PATCH 129/160] chore: removed = for version --- target_chains/ethereum/sdk/stylus/Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index ba40f7a615..786597f9e4 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -31,10 +31,10 @@ rust_2021_compatibility = { level = "warn", priority = -1 } [workspace.dependencies] # stylus-related -stylus-sdk = { version = "=0.6.0", default-features = false } +stylus-sdk = { version = "0.6.0", default-features = false } mini-alloc = "0.4.2" -alloy = { version = "=0.1.4", features = [ +alloy = { version = "0.1.4", features = [ "contract", "network", "providers", @@ -47,11 +47,11 @@ alloy = { version = "=0.1.4", features = [ # Even though `alloy` includes `alloy-primitives` and `alloy-sol-types` we need # to keep both versions for compatibility with the Stylus SDK. Once they start # using `alloy` we can remove these. -alloy-primitives = { version = "=0.7.6", default-features = false } -alloy-sol-types = { version = "=0.7.6", default-features = false } -alloy-sol-macro = { version = "=0.7.6", default-features = false } -alloy-sol-macro-expander = { version = "=0.7.6", default-features = false } -alloy-sol-macro-input = { version = "=0.7.6", default-features = false } +alloy-primitives = { version = "0.7.6", default-features = false } +alloy-sol-types = { version = "0.7.6", default-features = false } +alloy-sol-macro = { version = "0.7.6", default-features = false } +alloy-sol-macro-expander = { version = "0.7.6", default-features = false } +alloy-sol-macro-input = { version = "0.7.6", default-features = false } dotenv = "0.15.0" const-hex = { version = "1.11.1", default-features = false } From 88316829d0ae28adafbab0dd9c451628bbb956be Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:52:54 +0000 Subject: [PATCH 130/160] forge install: forge-std v1.9.4 --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules index 5d645e6a00..b2d56b06b6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "lazer/contracts/evm/lib/openzeppelin-contracts-upgradeable"] path = lazer/contracts/evm/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000000..1eea5bae12 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 88ace136c843fba555d349971ce04e2b2dc55f87 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:53:11 +0000 Subject: [PATCH 131/160] forge install: openzeppelin-foundry-upgrades v0.3.6 --- .gitmodules | 3 +++ lib/openzeppelin-foundry-upgrades | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index b2d56b06b6..eef57e90af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000000..16e0ae21e0 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 From 1cd711e528aff6cd6fac6ad29424707127b8b5c2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 16:55:04 +0000 Subject: [PATCH 132/160] forge install: openzeppelin-contracts-upgradeable v4.9.6 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index eef57e90af..c695812905 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,3 +20,6 @@ [submodule "lib/openzeppelin-foundry-upgrades"] path = lib/openzeppelin-foundry-upgrades url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000000..2d081f24ca --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 2d081f24cac1a867f6f73d512f2022e1fa987854 From 841af31ac276b868330109c7b4bbe7770bc08579 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:02:18 +0000 Subject: [PATCH 133/160] chore: Remove MIT license --- .../ethereum/sdk/stylus/licenses/MIT | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/licenses/MIT diff --git a/target_chains/ethereum/sdk/stylus/licenses/MIT b/target_chains/ethereum/sdk/stylus/licenses/MIT deleted file mode 100644 index 686b725588..0000000000 --- a/target_chains/ethereum/sdk/stylus/licenses/MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright 2023 YOUR COMPANY - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file From f6a5fe6546d11fac0d3e2a9a84121e9b1d57b79d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:06:12 +0000 Subject: [PATCH 134/160] chore: script changes --- target_chains/ethereum/sdk/stylus/scripts/bench.sh | 2 +- target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index f7ea7bbca2..0f226d85d1 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -13,7 +13,7 @@ cd "nitro-testnode" ./test-node.bash script send-l2 --to address_$WALLER_ADDRESS --ethamount 0.1 cd .. -cd "pyth-solidity" +cd "pyth-mock-solidity" deployed_to=$( forge script ./script/MockPyth.s.sol:MockPythScript \ --rpc-url "$RPC_URL" \ diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index 8dc7fa36e5..7d2e395444 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -10,7 +10,7 @@ cd "nitro-testnode" ./test-node.bash script send-l2 --to address_$WALLER_ADDRESS --ethamount 0.1 cd .. -cd "pyth-solidity" +cd "pyth-mock-solidity" deployed_to=$( forge script ./script/MockPyth.s.sol:MockPythScript \ --rpc-url "$RPC_URL" \ From ce78d45d50dde2b8eeb32df81e939168f067f60e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:07:42 +0000 Subject: [PATCH 135/160] chore: removed dependices --- .../ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std | 1 - .../sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts | 1 - .../stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades | 1 - 3 files changed, 3 deletions(-) delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts delete mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std deleted file mode 160000 index 1eea5bae12..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts deleted file mode 160000 index dc44c9f1a4..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dc44c9f1a4c3b10af99492eed84f83ed244203f6 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades deleted file mode 160000 index 16e0ae21e0..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 From f693364c6abcd6bbb25667d4b38c2d064b1d095d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:07:47 +0000 Subject: [PATCH 136/160] forge install: forge-std v1.9.4 --- .gitmodules | 3 +++ .../ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std diff --git a/.gitmodules b/.gitmodules index c695812905..28ede6321c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std"] + path = target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std new file mode 160000 index 0000000000..1eea5bae12 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 7a63cfdf0c4f521208f07bc8aa191664ba44e49c Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:07:56 +0000 Subject: [PATCH 137/160] forge install: openzeppelin-foundry-upgrades v0.3.6 --- .gitmodules | 3 +++ .../pyth-mock-solidity/lib/openzeppelin-foundry-upgrades | 1 + 2 files changed, 4 insertions(+) create mode 160000 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades diff --git a/.gitmodules b/.gitmodules index 28ede6321c..cc00f92d12 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,3 +26,6 @@ [submodule "target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std"] path = target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades"] + path = target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades new file mode 160000 index 0000000000..16e0ae21e0 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 From 42903d5d11848af44371643b00b12bbbe5bfeb80 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 17:20:48 +0000 Subject: [PATCH 138/160] chore:added description to script --- target_chains/ethereum/sdk/stylus/scripts/bench.sh | 4 ++++ target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh | 3 +++ target_chains/ethereum/sdk/stylus/scripts/deploy.sh | 3 +++ target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh | 5 +++++ target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh | 4 ++++ 5 files changed, 19 insertions(+) diff --git a/target_chains/ethereum/sdk/stylus/scripts/bench.sh b/target_chains/ethereum/sdk/stylus/scripts/bench.sh index 0f226d85d1..7a1f497f9b 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/bench.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/bench.sh @@ -1,4 +1,8 @@ #!/bin/bash +# This Bash script deploys and benchmarks a mock Pyth contract in a simulated Ethereum environment. +# It sends an L2 transaction using a test node, captures the deployed contract address, and compiles Rust-based contracts to WebAssembly. +# Finally, it runs benchmarking to measure gas usage and performance. + set -e MYDIR=$(realpath "$(dirname "$0")") diff --git a/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh index f1ae62db2d..2048d0a8e4 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/check-wasm.sh @@ -1,4 +1,7 @@ #!/bin/bash +# This script compiles Rust contracts to WebAssembly and validates the .wasm binaries for correctness using cargo stylus check. +# It retrieves crate names from Cargo.toml files in the examples directory and ensures each compiled contract is valid + set -e mydir=$(dirname "$0") diff --git a/target_chains/ethereum/sdk/stylus/scripts/deploy.sh b/target_chains/ethereum/sdk/stylus/scripts/deploy.sh index 65b77e6efc..9f828aff0c 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/deploy.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/deploy.sh @@ -1,4 +1,7 @@ #!/bin/bash +# This script compiles Rust contracts to WebAssembly and deploys the .wasm binaries to a blockchain using `cargo stylus deploy`. +# It retrieves crate names from Cargo.toml files in the examples directory and automates the deployment process for each contract. + set -e mydir=$(dirname "$0") diff --git a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh index 7d2e395444..da5652544d 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/e2e-tests.sh @@ -1,3 +1,8 @@ +#!/bin/bash +# This script automates the deployment and testing of Rust-based smart contracts. +# It sends L2 transactions, deploys the MockPyth contract using Forge, and captures its address. +# Finally, it compiles contracts to WebAssembly and runs end-to-end tests using a nightly Rust toolchain. + MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" diff --git a/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh index 4e807379da..44556759d1 100755 --- a/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh +++ b/target_chains/ethereum/sdk/stylus/scripts/nitro-testnode.sh @@ -1,4 +1,8 @@ #!/bin/bash +# This script manages the Nitro test node for deployment and testing purposes. +# It supports initializing the test node, running it in detached mode, or shutting it down. +# Options include cloning the Nitro test node repo if not present, and starting or stopping Docker containers as needed. + MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" || exit From 45d2d8dbbb19de7a3712ee6953688ca431222538 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 17:21:24 +0000 Subject: [PATCH 139/160] Update target_chains/ethereum/sdk/stylus/README.md Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 1d5708bb43..9bcb7c4249 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -24,7 +24,7 @@ For the latest changes from the `main` branch, you can also specify a git depend ```toml [dependencies] -pyth-stylus = { git = "URL" } +pyth-stylus = { git = "https://github.com/pyth-network/pyth-crosschain.git" } ``` ## Example Usage From 3912338f7fe5ee40fa426b14a5a1aae1b047c052 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 17:22:21 +0000 Subject: [PATCH 140/160] Update target_chains/ethereum/sdk/stylus/README.md Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 9bcb7c4249..5a81cb960f 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -31,7 +31,7 @@ pyth-stylus = { git = "https://github.com/pyth-network/pyth-crosschain.git" } To consume prices, use the functions interface. Be sure to read the function documentation to ensure safe use of price data. -For example, to read the latest price, call [`getPriceNoOlderThan`](https://github.com/Ifechukwudaniel/pyth-crosschain/blob/stylus-sdk/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs) with the Price ID of the price feed you are interested in: +For example, to read the latest price, call [`getPriceNoOlderThan`](https://github.com/pyth-network/pyth-crosschain/blob/stylus-sdk/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs) with the Price ID of the price feed you are interested in: ```rust use pyth_stylus::pyth::functions::get_price_no_older_than; From 50c7cb9b46c57e1acfbe6415bada9591db0356c2 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 17:42:02 +0000 Subject: [PATCH 141/160] Update target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs index 205ab5da3a..2dc86c9887 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mod.rs @@ -9,5 +9,5 @@ pub mod mock; /// Functions for interacting with the Pyth oracle. pub mod functions; -/// Conttract for interacting with the Pyth oracle. +/// Contract for interacting with the Pyth oracle. pub mod pyth_contract; From a399be41e94c480b7acacfe8554bb9346ac533f6 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Mon, 25 Nov 2024 17:43:23 +0000 Subject: [PATCH 142/160] Update target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs Co-authored-by: Pavel Strakhov --- target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs index e1f2899b4c..c5dca30128 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/errors.rs @@ -2,7 +2,7 @@ use alloy_sol_types::sol; use stylus_sdk::{call::MethodError, prelude::*}; sol! { - // Function arguments are invalid (e.g., the arguments lengths mismatch) + // Function arguments are invalid (e.g., the arguments lengths mismatch) // Signature: 0xa9cb9e0d #[derive(Debug)] #[allow(missing_docs)] From 4e00e9f27480b37cdc7f0e7a35be21c44195e3a1 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 19:01:27 +0000 Subject: [PATCH 143/160] chore: changed // to /// and remove licenses because of root licenese --- .../sdk/stylus/contracts/src/pyth/mock.rs | 34 +-- .../ethereum/sdk/stylus/licenses/Apache-2.0 | 201 ------------------ .../ethereum/sdk/stylus/licenses/COPYRIGHT.md | 5 - .../ethereum/sdk/stylus/licenses/DCO.txt | 34 --- 4 files changed, 17 insertions(+), 257 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/licenses/Apache-2.0 delete mode 100644 target_chains/ethereum/sdk/stylus/licenses/COPYRIGHT.md delete mode 100644 target_chains/ethereum/sdk/stylus/licenses/DCO.txt diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index fed01785d2..bf7e7a5d7b 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -11,7 +11,7 @@ use alloy_primitives::{Bytes, Uint, B256, U256}; use alloy_sol_types::{sol_data::Uint as SolUInt, SolType, SolValue}; use stylus_sdk::{abi::Bytes as AbiBytes, evm, msg, prelude::*}; -///Decode data type PriceFeed and uint64 +////Decode data type PriceFeed and uint64 pub type DecodeDataType = (PriceFeed, SolUInt<64>); sol_storage! { @@ -55,21 +55,21 @@ impl MockPythContract { self.valid_time_period.get() } - // Takes an array of encoded price feeds and stores them. - // You can create this data either by calling createPriceFeedUpdateData or - // by using web3.js or ethers abi utilities. - // @note: The updateData expected here is different from the one used in the main contract. - // In particular, the expected format is: - // [ - // abi.encode( - // PythStructs.PriceFeed( - // bytes32 id, - // PythStructs.Price price, - // PythStructs.Price emaPrice - // ), - // uint64 prevPublishTime - // ) - // ] + /// Takes an array of encoded price feeds and stores them. + /// You can create this data either by calling createPriceFeedUpdateData or + /// by using web3.js or ethers abi utilities. + /// @note: The updateData expected here is different from the one used in the main contract. + /// In particular, the expected format is: + /// [ + /// abi.encode( + /// PythStructs.PriceFeed( + /// bytes32 id, + /// PythStructs.Price price, + /// PythStructs.Price emaPrice + /// ), + /// uint64 prevPublishTime + /// ) + /// ] #[payable] fn update_price_feeds( @@ -264,7 +264,7 @@ mod tests { use alloy_sol_types::SolType; use stylus_sdk::abi::Bytes; - // Updated constants to use uppercase naming convention + /// Updated constants to use uppercase naming convention const PRICE: i64 = 1000; const CONF: u64 = 1000; const EXPO: i32 = 1000; diff --git a/target_chains/ethereum/sdk/stylus/licenses/Apache-2.0 b/target_chains/ethereum/sdk/stylus/licenses/Apache-2.0 deleted file mode 100644 index 389377b7a9..0000000000 --- a/target_chains/ethereum/sdk/stylus/licenses/Apache-2.0 +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 YOUR COMPANY - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/licenses/COPYRIGHT.md b/target_chains/ethereum/sdk/stylus/licenses/COPYRIGHT.md deleted file mode 100644 index 7fb8518b24..0000000000 --- a/target_chains/ethereum/sdk/stylus/licenses/COPYRIGHT.md +++ /dev/null @@ -1,5 +0,0 @@ -# Licensing Information - -Copyright 2023 YOUR COMPANY - -Except as otherwise noted (below and/or in individual files), this project is licensed under the Apache License, Version 2.0 ([`LICENSE-APACHE`](Apache-2.0) or http://www.apache.org/licenses/LICENSE-2.0) or the MIT license, ([`LICENSE-MIT`](MIT) or http://opensource.org/licenses/MIT), at your option. \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/licenses/DCO.txt b/target_chains/ethereum/sdk/stylus/licenses/DCO.txt deleted file mode 100644 index 49b8cb0549..0000000000 --- a/target_chains/ethereum/sdk/stylus/licenses/DCO.txt +++ /dev/null @@ -1,34 +0,0 @@ -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. From 86e1b5b0c6f553a1e07f05a4f45df09cd90f8022 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 19:02:39 +0000 Subject: [PATCH 144/160] chore: Apache-2.0 license --- target_chains/ethereum/sdk/stylus/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/Cargo.toml b/target_chains/ethereum/sdk/stylus/Cargo.toml index 786597f9e4..6bd6741c50 100644 --- a/target_chains/ethereum/sdk/stylus/Cargo.toml +++ b/target_chains/ethereum/sdk/stylus/Cargo.toml @@ -19,7 +19,7 @@ resolver = "2" [workspace.package] authors = ["Ifechukwu Daniel"] edition = "2021" -license = "MIT" +license = "Apache-2.0" repository = "https://github.com/pyth-network/pyth-crosschain" version = "0.1.0" From a91e478125a3e83ab1ba394551a223af16ba937f Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 19:06:45 +0000 Subject: [PATCH 145/160] chore: renamed file name pyth-solidity to pyth-mock-solidity --- .../ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json | 4 ++-- .../ethereum/sdk/stylus/pyth-mock-solidity/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json index 213e39f20b..5bc95beaca 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json @@ -1,11 +1,11 @@ { - "name": "pyth-solidity", + "name": "pyth-mock-solidity", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "pyth-solidity", + "name": "pyth-mock-solidity", "version": "1.0.0", "license": "ISC", "dependencies": { diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json index 372dfd8609..722c0961b3 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json @@ -1,5 +1,5 @@ { - "name": "pyth-solidity", + "name": "pyth-mock-solidity", "version": "1.0.0", "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", "main": "index.js", From f01d0e8b2b2512babf3b009085eeb5350514ba8e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 19:17:12 +0000 Subject: [PATCH 146/160] chore: removed commented code --- .../sdk/stylus/benches/src/function_calls.rs | 8 ++------ .../ethereum/sdk/stylus/benches/src/proxy_calls.rs | 13 ------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs index 3eba594781..d5dea12094 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/function_calls.rs @@ -62,10 +62,8 @@ pub async fn run_with( let _ = receipt!(contract.getEmaPriceNoOlderThan())?; let _ = receipt!(contract.getUpdateFee())?; let _ = receipt!(contract.getValidTimePeriod())?; - // let _ = receipt!(contract.updatePriceFeeds())?; - // let _ = receipt!(contract.updatePriceFeedsIfNecessary())?; - - // IMPORTANT: Order matters! + + /// IMPORTANT: Order matters! use FunctionCall::*; #[rustfmt::skip] let receipts = vec![ @@ -75,8 +73,6 @@ pub async fn run_with( (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan())?), (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee())?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), - //(updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds())?), - //(updatePriceFeedsIfNecessaryCall::SIGNATURE, receipt!(contract.updatePriceFeedsIfNecessary())?) ]; receipts diff --git a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs index 0dda002530..c45e88e784 100644 --- a/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs +++ b/target_chains/ethereum/sdk/stylus/benches/src/proxy_calls.rs @@ -73,17 +73,6 @@ pub async fn run_with( let _ = receipt!(contract.getUpdateFee(data.clone()))?; let _ = receipt!(contract.updatePriceFeeds(data.clone()))?; - // I will add updatePriceFeedsIfNecessary benchmark when it's ready - //println!("{data:?} , {ids:?} "); - // let _ = contract - // .updatePriceFeedsIfNecessary( - // data.clone(), - // ids.clone(), - // vec![100, 100, 100], - // ) - // .send() - // .await?; - // IMPORTANT: Order matters! use ProxyCall::*; #[rustfmt::skip] @@ -94,8 +83,6 @@ pub async fn run_with( (getEmaPriceNoOlderThanCall::SIGNATURE, receipt!(contract.getEmaPriceNoOlderThan(id, time_frame))?), (getValidTimePeriodCall::SIGNATURE, receipt!(contract.getValidTimePeriod())?), (getUpdateFeeCall::SIGNATURE, receipt!(contract.getUpdateFee(data.clone()))?), - //(updatePriceFeedsCall::SIGNATURE, receipt!(contract.updatePriceFeeds(data.clone()))?), - //(updatePriceFeedsIfNecessaryCall::SIGNATURE, receipt!(contract.updatePriceFeedsIfNecessary(data.clone(), ids.clone(), vec![0,0,0]))?) ]; receipts From a87645b4ba27c3171fe3367c82890827d778f889 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 20:19:18 +0000 Subject: [PATCH 147/160] chore: foundry test workflow --- .../workflows/test.yml => .github/workflows/foundry-test.yml} | 2 ++ 1 file changed, 2 insertions(+) rename target_chains/ethereum/sdk/stylus/{pyth-mock-solidity/.github/workflows/test.yml => .github/workflows/foundry-test.yml} (94%) diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml similarity index 94% rename from target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml rename to target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml index 762a2966f7..cd3b638cd1 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/.github/workflows/test.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml @@ -15,6 +15,8 @@ jobs: name: Foundry project runs-on: ubuntu-latest + script: + - cd pyth-mock-solidity/ steps: - uses: actions/checkout@v4 with: From ad0468f542fa064f42f2d11886455d623bdacd66 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 21:59:28 +0000 Subject: [PATCH 148/160] chore: workflows --- .../stylus/.github/workflows/check-links.yml | 37 +++++ .../stylus/.github/workflows/check-wasm.yml | 31 +++++ .../sdk/stylus/.github/workflows/check.yml | 130 ++++++++++++++++++ .../stylus/.github/workflows/e2e-tests.yml | 47 +++++++ .../stylus/.github/workflows/foundry-test.yml | 23 ++-- .../stylus/.github/workflows/gas-bench.yml | 40 ++++++ .../sdk/stylus/.github/workflows/nostd.yml | 38 +++++ 7 files changed, 332 insertions(+), 14 deletions(-) create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml create mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml new file mode 100644 index 0000000000..36ec10460e --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml @@ -0,0 +1,37 @@ +name: Check Links +# This workflow checks that all links in the documentation are valid. +# It does this for antora docs(adoc) and markdown files. +# We prefer lycheeverse because it is faster, but doesn't support adoc files yet(https://github.com/lycheeverse/lychee/issues/291) +# Because of that, we use linkspector for adoc files and lychee for md files. +on: + push: + branches: [ main, v*, release/* ] + pull_request: + branches: [ main, v*, release/* ] + +jobs: + check-links-md: + name: Check Markdown Links + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Link Checker + uses: lycheeverse/lychee-action@v2 + with: + args: --no-progress './**/*.md' + fail: true + + check-links-adoc: + name: Check AsciiDoc Links + runs-on: ubuntu-latest + # We only run the AsciiDoc link checker on v* branches because: + # 1. The main branch may contain documentation that is not yet published. + if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') + steps: + - uses: actions/checkout@v4 + + - name: Run linkspector + uses: umbrelladocs/action-linkspector@v1 + with: + fail_on_error: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml new file mode 100644 index 0000000000..aee66a6ec7 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml @@ -0,0 +1,31 @@ +name: check-wasm +# This workflow checks that the compiled wasm binary of every example contract +# can be deployed to Arbitrum Stylus. +permissions: + contents: read +on: + push: + branches: [ main, release/* ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + check-wasm: + name: Check WASM binary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: "" + + - name: Install cargo-stylus + run: cargo install cargo-stylus@0.5.3 + + - name: Run wasm check + run: ./scripts/check-wasm.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml new file mode 100644 index 0000000000..8fe3e95f8b --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml @@ -0,0 +1,130 @@ +name: check +# This workflow runs whenever a PR is opened or updated, or a commit is pushed +# to main. It runs several checks: +# - fmt: checks that the code is formatted according to `rustfmt`. +# - clippy: checks that the code does not contain any `clippy` warnings. +# - doc: checks that the code can be documented without errors. +# - hack: check combinations of feature flags. +# - typos: checks for typos across the repo. +permissions: + contents: read +# This configuration allows maintainers of this repo to create a branch and +# pull request based on the new branch. Restricting the push trigger to the +# main branch ensures that the PR only gets built once. +on: + push: + branches: [ main, release/* ] + pull_request: +# If new code is pushed to a PR branch, then cancel in progress workflows for +# that PR. Ensures that we don't waste CI time, and returns results quicker. +# https://github.com/jonhoo/rust-ci-conf/pull/5 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + fmt: + runs-on: ubuntu-latest + name: nightly / fmt + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install rust + # We run in nightly to make use of some features only available there. + # Check out `rustfmt.toml` to see which ones. + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + components: rustfmt + rustflags: "" + + - name: Check formatting + run: cargo fmt --all --check + clippy: + runs-on: ubuntu-latest + name: ${{ matrix.toolchain }} / clippy + permissions: + contents: read + checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta + # channels. + toolchain: [ stable, beta ] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install rust ${{ matrix.toolchain }} + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + rustflags: "" + + - name: Cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} + doc: + # Run docs generation on nightly rather than stable. This enables features + # like https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html + # which allows an API be documented as only available in some specific + # platforms. + runs-on: ubuntu-latest + name: nightly / doc + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + rustflags: "" + + - name: Cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + hack: + # `cargo-hack` checks combinations of feature flags to ensure that features + # are all additive which is required for feature unification. + runs-on: ubuntu-latest + name: ubuntu / stable / features + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: wasm32-unknown-unknown + rustflags: "" + + - name: Cargo install cargo-hack + uses: taiki-e/install-action@cargo-hack + # Intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 + # `--feature-powerset` runs for every combination of features. Note that + # target in this context means one of `--lib`, `--bin`, etc, and not the + # target triple. + - name: Cargo hack + run: cargo hack check --feature-powerset --depth 2 --release --target wasm32-unknown-unknown --skip std --workspace --exclude e2e --exclude basic-example-script --exclude benches + typos: + runs-on: ubuntu-latest + name: ubuntu / stable / typos + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + + - name: Check spelling of files in the workspace + uses: crate-ci/typos@v1.27.3 diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000000..6e85b8bf3e --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml @@ -0,0 +1,47 @@ +# This workflow runs our end-to-end tests suite. +# +# It roughly follows these steps: +# - Install rust +# - Install `cargo-stylus` +# - Install `solc` +# - Spin up `nitro-testnode` +# +# Contract deployments and account funding happen on a per-test basis. +name: e2e +permissions: + contents: read +on: + push: + branches: [ main, release/* ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + required: + name: tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + cache-key: "e2e-tests" + rustflags: "" + + - name: Install cargo-stylus + run: cargo install cargo-stylus@0.5.3 + + - name: Install solc + run: | + curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux + sudo mv solc-static-linux /usr/bin/solc + sudo chmod a+x /usr/bin/solc + + - name: Setup nitro node + run: ./scripts/nitro-testnode.sh -d -i + - name: run integration tests + run: ./scripts/e2e-tests.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml index cd3b638cd1..651cbb7738 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml @@ -10,15 +10,11 @@ env: jobs: check: - strategy: - fail-fast: true - name: Foundry project runs-on: ubuntu-latest - script: - - cd pyth-mock-solidity/ steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 with: submodules: recursive @@ -28,20 +24,19 @@ jobs: version: nightly - name: Show Forge version - run: | - forge --version + run: forge --version + + - name: Change directory to pyth-mock-solidity + run: cd pyth-mock-solidity - name: Run Forge fmt - run: | - forge fmt --check + run: forge fmt --check id: fmt - name: Run Forge build - run: | - forge build --sizes + run: forge build --sizes id: build - name: Run Forge tests - run: | - forge test -vvv + run: forge test -vvv id: test diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml new file mode 100644 index 0000000000..62c8ed73c5 --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml @@ -0,0 +1,40 @@ +name: gas-bench +# This workflow checks that the compiled wasm binary of every example contract +# can be deployed to Arbitrum Stylus. +permissions: + contents: read +on: + push: + branches: [ main, release/* ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + required: + name: Gas usage report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + cache-key: "gas-bench" + rustflags: "" + + - name: Install cargo-stylus + run: cargo install cargo-stylus@0.5.3 + + - name: Install solc + run: | + curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux + sudo mv solc-static-linux /usr/bin/solc + sudo chmod a+x /usr/bin/solc + + - name: Setup nitro node + run: ./scripts/nitro-testnode.sh -d -i + - name: run benches + run: ./scripts/bench.sh diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml new file mode 100644 index 0000000000..409b76f5cf --- /dev/null +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml @@ -0,0 +1,38 @@ +# This workflow checks whether the library is able to run without the std +# library. See `check.yml` for information about how the concurrency +# cancellation and workflow triggering works. +name: no-std +permissions: + contents: read +on: + push: + branches: [ main, release/* ] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +env: + CARGO_TERM_COLOR: always +jobs: + nostd: + runs-on: ubuntu-latest + name: ${{ matrix.target }} + strategy: + matrix: + target: [ wasm32-unknown-unknown ] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + rustflags: "" + + - name: Add rust targets ${{ matrix.target }} + run: rustup target add ${{ matrix.target }} + + - name: Cargo check + run: cargo check --release --target ${{ matrix.target }} --no-default-features From d3167e0d7719d4ed661a9cd4bc3f7b3c8264286a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 21:59:52 +0000 Subject: [PATCH 149/160] chore: changed readme to rust --- target_chains/ethereum/sdk/stylus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/README.md b/target_chains/ethereum/sdk/stylus/README.md index 5a81cb960f..4d163debfb 100644 --- a/target_chains/ethereum/sdk/stylus/README.md +++ b/target_chains/ethereum/sdk/stylus/README.md @@ -1,6 +1,6 @@ # Pyth Stylus SDK -This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle in Solidity. It also includes the [Pyth Interface ABI](./abis/IPyth.json), which can be used in your libraries to interact with the Pyth contract. +This package provides utilities for consuming prices from the [Pyth Network](https://pyth.network/) Oracle in Rust with Stylus. It also includes the [Pyth Interface ABI](./abis/IPyth.json), which can be used in your libraries to interact with the Pyth contract. It is **strongly recommended** to follow the [consumer best practices](https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices) when consuming data from Pyth. From 7f53584c15c9e3553400712066281512bd50c165 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 25 Nov 2024 22:05:51 +0000 Subject: [PATCH 150/160] chore: fixed description mock solidity --- .../ethereum/sdk/stylus/pyth-mock-solidity/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json index 722c0961b3..93b76ae6db 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json @@ -1,7 +1,7 @@ { "name": "pyth-mock-solidity", "version": "1.0.0", - "description": "**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**", + "description": "** Deploy mock solidity pyth contract using the pyth solidity sdk **", "main": "index.js", "directories": { "lib": "lib", From 605fc5891de014744c5d9215c94084150e00aa86 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 00:05:03 +0000 Subject: [PATCH 151/160] chore: fixed zero case --- .../ethereum/sdk/stylus/contracts/src/pyth/mock.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index bf7e7a5d7b..1aed31f8bb 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -26,11 +26,11 @@ sol_storage! { impl MockPythContract { fn initialize( &mut self, - single_update_fee_in_wei: Uint<256, 4>, - valid_time_period: Uint<256, 4>, + single_update_fee_in_wei: U256, + valid_time_period: U256, ) -> Result<(), Vec> { - if single_update_fee_in_wei <= U256::from(0) - || valid_time_period <= U256::from(0) + if single_update_fee_in_wei <= U256::ZERO + || valid_time_period <= U256::ZERO { return Err(Error::InvalidArgument(InvalidArgument {}).into()); } @@ -41,7 +41,7 @@ impl MockPythContract { fn query_price_feed(&self, id: B256) -> Result, Vec> { let price_feed = self.price_feeds.get(id).to_price_feed(); - if price_feed.id.eq(&B256::ZERO) { + if price_feed.id.is_zero() { return Err(Error::PriceFeedNotFound(PriceFeedNotFound {}).into()); } Ok(price_feed.abi_encode()) @@ -51,7 +51,7 @@ impl MockPythContract { self.price_feeds.getter(id).id.is_empty() } - fn get_valid_time_period(&self) -> Uint<256, 4> { + fn get_valid_time_period(&self) -> U256 { self.valid_time_period.get() } @@ -104,7 +104,7 @@ impl MockPythContract { Ok(()) } - fn get_update_fee(&self, update_data: Vec) -> Uint<256, 4> { + fn get_update_fee(&self, update_data: Vec) -> U256 { self.single_update_fee_in_wei.get() * U256::from(update_data.len()) } #[payable] From 233f68bad2a61199e0972b72816ca9a8927ee647 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 02:14:53 +0000 Subject: [PATCH 152/160] chore: change all use case of gt and lt to > and < --- .../sdk/stylus/contracts/src/pyth/mock.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 1aed31f8bb..167096e50a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -7,7 +7,7 @@ use crate::{ utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE, }; use alloc::vec::Vec; -use alloy_primitives::{Bytes, Uint, B256, U256}; +use alloy_primitives::{Bytes, B256, U256}; use alloy_sol_types::{sol_data::Uint as SolUInt, SolType, SolValue}; use stylus_sdk::{abi::Bytes as AbiBytes, evm, msg, prelude::*}; @@ -77,7 +77,7 @@ impl MockPythContract { update_data: Vec, ) -> Result<(), Vec> { let required_fee = self.get_update_fee(update_data.clone()); - if required_fee.lt(&U256::from(msg::value())) { + if required_fee < msg::value() { return Err(Error::InsufficientFee(InsufficientFee {}).into()); } @@ -88,8 +88,8 @@ impl MockPythContract { CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec() })?; let last_publish_time = - &self.price_feeds.get(price_feed_data.id).price.publish_time; - if last_publish_time.lt(&price_feed_data.price.publish_time) { + &self.price_feeds.get(price_feed_data.id).price.publish_time.get(); + if last_publish_time < &price_feed_data.price.publish_time { self.price_feeds .setter(price_feed_data.id) .set(price_feed_data); @@ -173,7 +173,7 @@ impl MockPythContract { unique: bool, ) -> Result, Vec> { let required_fee = self.get_update_fee(update_data.clone()); - if required_fee.lt(&U256::from(msg::value())) { + if required_fee < msg::value() { return Err(Error::InsufficientFee(InsufficientFee {}).into()); } @@ -198,8 +198,7 @@ impl MockPythContract { .price_feeds .get(feeds[i].id) .price - .publish_time - .lt(&publish_time) + .publish_time.get() < publish_time { self.price_feeds.setter(feeds[i].id).set(feeds[i]); evm::log(PriceFeedUpdate { @@ -211,9 +210,9 @@ impl MockPythContract { } if feeds[i].id == price_ids[i] { - if publish_time.gt(&U256::from(min_publish_time)) - && publish_time.le(&U256::from(max_publish_time)) - && (!unique || prev_publish_time.lt(&min_publish_time)) + if publish_time > U256::from(min_publish_time) + && publish_time <= U256::from(max_publish_time) + && (!unique || prev_publish_time < min_publish_time) { break; } else { From d0973f28dfa2843119be2c98e5fb692f865e4e3b Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 02:15:09 +0000 Subject: [PATCH 153/160] chore: workflow --- .../workflows/publish-pythnet-stylus-sdk.yml | 20 ++++++++++ .../stylus/.github/workflows/check-links.yml | 37 ------------------- .../stylus/.github/workflows/check-wasm.yml | 13 +++++-- .../sdk/stylus/.github/workflows/check.yml | 12 +++--- .../stylus/.github/workflows/e2e-tests.yml | 9 ++++- .../stylus/.github/workflows/foundry-test.yml | 11 ++++-- .../stylus/.github/workflows/gas-bench.yml | 9 ++++- .../sdk/stylus/.github/workflows/nostd.yml | 9 ++++- 8 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/publish-pythnet-stylus-sdk.yml delete mode 100644 target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml diff --git a/.github/workflows/publish-pythnet-stylus-sdk.yml b/.github/workflows/publish-pythnet-stylus-sdk.yml new file mode 100644 index 0000000000..d1b9458c7f --- /dev/null +++ b/.github/workflows/publish-pythnet-stylus-sdk.yml @@ -0,0 +1,20 @@ +name: Publish Pyth Stylus SDK to crates.io + +on: + push: + tags: + - pythnet-stylus-sdk-v* +# jobs: +# publish-pythnet-sdk: +# name: Publish Pythnet SDK +# runs-on: ubuntu-latest +# steps: +# - name: Checkout sources +# uses: actions/checkout@v2 + +# - run: cargo publish --token ${CARGO_REGISTRY_TOKEN} +# env: +# CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} +# working-directory: "pythnet/pythnet_sdk" + +#Just seetinh up this script \ No newline at end of file diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml deleted file mode 100644 index 36ec10460e..0000000000 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check-links.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Check Links -# This workflow checks that all links in the documentation are valid. -# It does this for antora docs(adoc) and markdown files. -# We prefer lycheeverse because it is faster, but doesn't support adoc files yet(https://github.com/lycheeverse/lychee/issues/291) -# Because of that, we use linkspector for adoc files and lychee for md files. -on: - push: - branches: [ main, v*, release/* ] - pull_request: - branches: [ main, v*, release/* ] - -jobs: - check-links-md: - name: Check Markdown Links - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Link Checker - uses: lycheeverse/lychee-action@v2 - with: - args: --no-progress './**/*.md' - fail: true - - check-links-adoc: - name: Check AsciiDoc Links - runs-on: ubuntu-latest - # We only run the AsciiDoc link checker on v* branches because: - # 1. The main branch may contain documentation that is not yet published. - if: startsWith(github.ref, 'refs/heads/v') || startsWith(github.base_ref, 'v') - steps: - - uses: actions/checkout@v4 - - - name: Run linkspector - uses: umbrelladocs/action-linkspector@v1 - with: - fail_on_error: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml index aee66a6ec7..f27d7b991b 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check-wasm.yml @@ -1,12 +1,17 @@ name: check-wasm # This workflow checks that the compiled wasm binary of every example contract # can be deployed to Arbitrum Stylus. -permissions: - contents: read on: - push: - branches: [ main, release/* ] pull_request: + paths: + - target_chains/ethereum/sdk/stylus/** ** + push: + branches: + - main + paths: + - target_chains/ethereum/sdk/stylus/** +permissions: + contents: read concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml index 8fe3e95f8b..6563489730 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml @@ -12,12 +12,14 @@ permissions: # pull request based on the new branch. Restricting the push trigger to the # main branch ensures that the PR only gets built once. on: - push: - branches: [ main, release/* ] pull_request: -# If new code is pushed to a PR branch, then cancel in progress workflows for -# that PR. Ensures that we don't waste CI time, and returns results quicker. -# https://github.com/jonhoo/rust-ci-conf/pull/5 + paths: + - target_chains/sui/contracts/** + push: + branches: + - main + paths: + - target_chains/sui/contracts/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml index 6e85b8bf3e..1ed85979a0 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml @@ -11,9 +11,14 @@ name: e2e permissions: contents: read on: - push: - branches: [ main, release/* ] pull_request: + paths: + - target_chains/sui/contracts/** + push: + branches: + - main + paths: + - target_chains/sui/contracts/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml index 651cbb7738..661c8e6a01 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml @@ -1,9 +1,14 @@ -name: CI +name: foundry-test on: - push: pull_request: - workflow_dispatch: + paths: + - target_chains/sui/contracts/** + push: + branches: + - main + paths: + - target_chains/sui/contracts/** env: FOUNDRY_PROFILE: ci diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml index 62c8ed73c5..65784789cb 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml @@ -4,9 +4,14 @@ name: gas-bench permissions: contents: read on: - push: - branches: [ main, release/* ] pull_request: + paths: + - target_chains/sui/contracts/** + push: + branches: + - main + paths: + - target_chains/sui/contracts/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml index 409b76f5cf..7b3d56bfeb 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml @@ -5,9 +5,14 @@ name: no-std permissions: contents: read on: - push: - branches: [ main, release/* ] pull_request: + paths: + - target_chains/sui/contracts/** + push: + branches: + - main + paths: + - target_chains/sui/contracts/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true From cea28438b507478dee684efccd6487cb01f2706e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 02:34:01 +0000 Subject: [PATCH 154/160] chore: replaced the file with the link --- .../ethereum/sdk/stylus/GUIDELINES.md | 294 +----------------- 1 file changed, 2 insertions(+), 292 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/GUIDELINES.md b/target_chains/ethereum/sdk/stylus/GUIDELINES.md index c60f07f49a..ae118a0bb5 100644 --- a/target_chains/ethereum/sdk/stylus/GUIDELINES.md +++ b/target_chains/ethereum/sdk/stylus/GUIDELINES.md @@ -1,295 +1,5 @@ # Engineering Guidelines -## Testing - -Code must be thoroughly tested with quality unit tests. - -We defer to the [Moloch Testing Guide] for specific recommendations, though -not all of it is relevant here, since this is a Rust project. Note the -introduction: - -> Tests should be written, not only to verify correctness of the target code, -> but to be comprehensively reviewed by other programmers. Therefore, for -> mission critical Solidity code, the quality of the tests are just as -> important (if not more so) than the code itself, and should be written with -> the highest standards of clarity and elegance. - -Every addition or change to the code must come with relevant and comprehensive -tests. - -Refactors should avoid simultaneous changes to tests. - -Flaky tests are not acceptable. - -The test suite should run automatically for every change in the repository, and -in pull requests tests must pass before merging. - -The test suite coverage must be kept as close to 100% as possible, enforced in -pull requests. - -In some cases unit tests may be insufficient and complementary techniques -should be used: - -1. Property-based tests (aka. fuzzing) for math-heavy code. -2. Formal verification for state machines. - -[Moloch Testing Guide]: https://github.com/MolochVentures/moloch/tree/master/test#readme - -## Peer review - -All changes must be submitted through pull requests and go through peer code -review. - -The review must be approached by the reviewer in a similar way as if it was an -audit of the code in question (but importantly it is not a substitute for and -should not be considered an audit). - -Reviewers should enforce code and project guidelines. - -External contributions must be reviewed separately by multiple maintainers. - -## Code style - -Rust code should be written in a consistent format enforced by `rustfmt`, -following the official [The Rust Style Guide]. See below for further -[Rust Conventions](#rust-conventions). - -The code should be simple and straightforward, prioritizing readability and -understandability. Consistency and predictability should be maintained across -the codebase. In particular, this applies to naming, which should be -systematic, clear, and concise. - -Sometimes these guidelines may be broken if doing so brings significant -efficiency gains, but explanatory comments should be added. - -The following code guidelines will help make code review smoother: - -### Use of `unwrap` and `expect` - -Use `unwrap` only in either of three circumstances: - -- Based on manual static analysis, you've concluded that it's impossible for - the code to panic; so unwrapping is _safe_. An example would be: - -```rust -let list = vec![a, b, c]; -let first = list.first().unwrap(); -``` - -- The panic caused by `unwrap` would indicate a bug in the software, and it - would be impossible to continue in that case. -- The `unwrap` is part of test code, ie. `cfg!(test)` is `true`. - -In the first and second case, document `unwrap` call sites with a comment -prefixed with `SAFETY:` that explains why it's safe to unwrap, eg. - -```rust -// SAFETY: Node IDs are valid ref strings. -let r = RefString::try_from(node.to_string()).unwrap(); -``` - -Use `expect` only if the function expects certain invariants that were not met, -either due to bad inputs, or a problem with the environment; and include the -expectation in the message. For example: - -```rust -logger::init(log::Level::Debug).expect("logger must only be initialized once"); -``` - -### Module imports - -Imports are organized in groups, from least specific to more specific: - -```rust -use std::collections::HashMap; // First, `std` imports. -use std::process; -use std::time; - -use git_ref_format as format; // Then, external dependencies. -use once_cell::sync::Lazy; - -use crate::crypto::PublicKey; // Finally, local crate imports. -use crate::storage::refs::Refs; -use crate::storage::RemoteId; -``` - -This is enforced by `rustfmt`. Note that this is a `nightly` feature. - -### Variable naming - -Use short 1-letter names when the variable scope is only a few lines, or the -context is -obvious, eg. - -```rust -if let Some(e) = result.err() { -... -} -``` - -Use 1-word names for function parameters or variables that have larger scopes: - -```rust -pub fn commit(repo: &Repository, sig: &Signature) -> Result { - ... -} -``` - -Use the most descriptive names for globals: - -```rust -pub const KEEP_ALIVE_DELTA: LocalDuration = LocalDuration::from_secs(30); -``` - -### Function naming - -Stay concise. Use the function doc comment to describe what the function does, -not the name. Keep in mind functions are in the context of the parent module -and/or object and repeating that would be redundant. - -## Dependencies - -Before adding any code dependencies, check with the maintainers if this is -okay. In general, we try not to add external dependencies unless it's -necessary. Dependencies increase counter-party risk, build-time, attack -surface, and make code harder to audit. - -We also optimize for binary size, which means we try to keep generated code to -a minimum and adding dependencies is one of the biggest sources of code bloat. - -## Documentation - -For contributors, project guidelines and processes must be documented publicly. - -For users, features must be abundantly documented. Documentation should include -answers to common questions, solutions to common problems, and recommendations -for critical decisions that the user may face. - -All changes to the core codebase (excluding tests, auxiliary scripts, etc.) -must be documented in a changelog, except for purely cosmetic or documentation -changes. - -All Rust items must be documented with documentation comments so that LSPs -display information about said items. - -## Automation - -Automation should be used as much as possible to reduce the possibility of -human error and forgetfulness. - -Automations that make use of sensitive credentials must use secure secret -management, and must be strengthened against attacks such as -[those on GitHub Actions worklows]. - -Some other examples of automation are: - -- Looking for common security vulnerabilities or errors in our code (eg. - reentrancy analysis). -- Keeping dependencies up to date and monitoring for vulnerable dependencies. - -[those on GitHub Actions worklows]: https://github.com/nikitastupin/pwnhub - -### Linting & formatting - -Always check your code with the linter (`clippy`), by running: - - $ cargo clippy --tests --all-features - -And make sure your code is formatted with, using: - - $ cargo +nightly fmt - -Finally, ensure there is no trailing whitespace anywhere. - -### Running tests - -Make sure all tests are passing with: - - $ cargo test --all-features - -### Running end-to-end tests - -In order to run end-to-end (e2e) tests, you should have the cargo stylus tool: - -```shell -cargo install cargo-stylus -``` - -Since most of the e2e tests use [koba](https://github.com/OpenZeppelin/koba) for deploying contracts, you need to -[install](https://docs.soliditylang.org/en/latest/installing-solidity.html#) the solidity compiler (`v0.8.24`). - -To run e2e tests, you need to have a local nitro test node up and running. -Run the following command and wait till script exit successfully: - -```shell -./scripts/nitro-testnode.sh -i -d -``` - -Then you will be able to run e2e tests: - -```shell -./scripts/e2e-tests.sh -``` - -### Checking the docs - -If you make documentation changes, you may want to check whether there are any -warnings or errors: - - $ cargo doc --all-features - -## Pull requests - -Pull requests are squash-merged to keep the `main` branch history clean. The -title of the pull request becomes the commit message, which should follow -[Semantic versioning]. - -Work in progress pull requests should be submitted as Drafts and should not be -prefixed with "WIP:". - -Branch names don't matter, and commit messages within a pull request mostly -don't matter either, although they can help the review process. - -## Writing commit messages - -A properly formed git commit subject line should always be able to complete the -following sentence: - - If applied, this commit will _____ - -In addition, it should be not capitalized and _must not_ include a period. We -prefix all commits by following [Conventional Commits] guidelines. For example, -the following message is well formed: - - feat(merkle): add single-leaf proof verification - -While these ones are **not**: `add single-leaf proof verification`, -`Added single-leaf proof verification`, `Add single-leaf proof verification`, -`frob: add single-leaf proof verification`, `feat: Add single-leaf proof -verification`. - -When it comes to formatting, here's a model git commit message[1]: - - type: lower-case, short (50 chars or less) summary - - More detailed explanatory text, if necessary. Wrap it to about 72 - characters or so. In some contexts, the first line is treated as the - subject of an email and the rest of the text as the body. The blank - line separating the summary from the body is critical (unless you omit - the body entirely); tools like rebase can get confused if you run the - two together. - - Write your commit message in the imperative: "Fix bug" and not "Fixed bug" - or "Fixes bug." This convention matches up with commit messages generated - by commands like git merge and git revert. - - Further paragraphs come after blank lines. - - - Bullet points are okay, too. - - Typically a hyphen or asterisk is used for the bullet, followed by a - single space, with blank lines in between, but conventions vary here. - - Use a hanging indent. - - -[The Rust Style Guide]: https://doc.rust-lang.org/nightly/style-guide/ +For best practices, coding standards, and other essential guidelines, please refer to the official [OpenZeppelin Rust Contracts Stylus Engineering Guidelines](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/GUIDELINES.md). +This document outlines the principles and practices for ths codebase , maintainable, and secure Rust code within the context of the Rust Contracts Stylus project. \ No newline at end of file From 0fdbb3e3538e3664a0b4061ce11c41ba2233f718 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 03:04:44 +0000 Subject: [PATCH 155/160] chore: refactor and removed unused code --- .../stylus/contracts/src/pyth/functions.rs | 2 +- .../sdk/stylus/contracts/src/pyth/mock.rs | 2 +- .../sdk/stylus/contracts/src/utils/helpers.rs | 79 ------------------- .../sdk/stylus/contracts/src/utils/mod.rs | 56 ++++++++++++- 4 files changed, 56 insertions(+), 83 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs index 488fdb8212..771f6e48b4 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/functions.rs @@ -5,7 +5,7 @@ use crate::pyth::types::{ parsePriceFeedUpdatesCall, parsePriceFeedUpdatesUniqueCall, updatePriceFeedsCall, updatePriceFeedsIfNecessaryCall, Price, PriceFeed, }; -use crate::utils::helpers::{call_helper, delegate_call_helper}; +use crate::utils::{call_helper, delegate_call_helper}; use alloc::vec::Vec; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_sol_types::SolType; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs index 167096e50a..ce9b575238 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/mock.rs @@ -4,7 +4,7 @@ use crate::pyth::functions::create_price_feed_update_data; use crate::pyth::types::{Price, PriceFeed, StoragePriceFeed}; use crate::{ pyth::errors::{FalledDecodeData, InsufficientFee, InvalidArgument}, - utils::helpers::CALL_RETDATA_DECODING_ERROR_MESSAGE, + utils::CALL_RETDATA_DECODING_ERROR_MESSAGE, }; use alloc::vec::Vec; use alloy_primitives::{Bytes, B256, U256}; diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs deleted file mode 100644 index 926fad11f9..0000000000 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/helpers.rs +++ /dev/null @@ -1,79 +0,0 @@ -use { - alloc::vec::Vec, - alloy_sol_types::{SolCall, SolType}, - stylus_sdk::{ - alloy_primitives::Address, - call::{call, delegate_call, static_call}, - storage::TopLevelStorage, - }, -}; - -/// The revert message when failing to decode the data -/// returned by an external contract call -pub const CALL_RETDATA_DECODING_ERROR_MESSAGE: &[u8] = - b"error decoding retdata"; - -/// Maps an error returned from an external contract call to a `Vec`, -/// which is the expected return type of external contract methods. -pub fn map_call_error(e: stylus_sdk::call::Error) -> Vec { - match e { - stylus_sdk::call::Error::Revert(msg) => msg, - stylus_sdk::call::Error::AbiDecodingFailed(_) => { - CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec() - } - } -} - -/// Performs a `delegate call` to the given address, calling the function -/// defined as a `SolCall` with the given arguments. - -pub fn delegate_call_helper( - storage: &mut impl TopLevelStorage, - address: Address, - args: as SolType>::RustType, -) -> Result> { - let calldata = C::new(args).abi_encode(); - let res = unsafe { - delegate_call(storage, address, &calldata).map_err(map_call_error)? - }; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) -} - -/// Performs a `staticcall` to the given address, calling the function defined as a `SolCall` with the given arguments - -pub fn static_call_helper( - storage: &impl TopLevelStorage, - address: Address, - args: as SolType>::RustType, -) -> Result> { - let calldata = C::new(args).abi_encode(); - let res = - static_call(storage, address, &calldata).map_err(map_call_error)?; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) -} - -/// Performs a `call` to the given address, calling the function -/// defined as a `SolCall` with the given arguments. -pub fn call_helper( - storage: &mut impl TopLevelStorage, - address: Address, - args: as SolType>::RustType, -) -> Result> { - let calldata = C::new(args).abi_encode(); - let res = call(storage, address, &calldata).map_err(map_call_error)?; - C::abi_decode_returns(&res, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) -} - -/// Decodes the data returned by an external contract call -pub fn decode_helper(data: &[u8]) -> Result> { - T::abi_decode(data, false /* validate */) - .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) -} - -/// Encodes the data to be returned by an external contract call -pub fn encode_helper(data: T::RustType) -> Vec { - T::abi_encode(&data) -} diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs index bf2c2d089a..7ba19e172a 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/utils/mod.rs @@ -1,2 +1,54 @@ -/// Utility functions for interacting with the Pyth oracle. -pub mod helpers; +use { + alloc::vec::Vec, + alloy_sol_types::{SolCall, SolType}, + stylus_sdk::{ + alloy_primitives::Address, + call::{call, delegate_call}, + storage::TopLevelStorage, + }, +}; + +/// The revert message when failing to decode the data +/// returned by an external contract call +pub const CALL_RETDATA_DECODING_ERROR_MESSAGE: &[u8] = + b"error decoding retdata"; + +/// Maps an error returned from an external contract call to a `Vec`, +/// which is the expected return type of external contract methods. +pub fn map_call_error(e: stylus_sdk::call::Error) -> Vec { + match e { + stylus_sdk::call::Error::Revert(msg) => msg, + stylus_sdk::call::Error::AbiDecodingFailed(_) => { + CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec() + } + } +} + +/// Performs a `delegate call` to the given address, calling the function +/// defined as a `SolCall` with the given arguments. + +pub fn delegate_call_helper( + storage: &mut impl TopLevelStorage, + address: Address, + args: as SolType>::RustType, +) -> Result> { + let calldata = C::new(args).abi_encode(); + let res = unsafe { + delegate_call(storage, address, &calldata).map_err(map_call_error)? + }; + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} + +/// Performs a `call` to the given address, calling the function +/// defined as a `SolCall` with the given arguments. +pub fn call_helper( + storage: &mut impl TopLevelStorage, + address: Address, + args: as SolType>::RustType, +) -> Result> { + let calldata = C::new(args).abi_encode(); + let res = call(storage, address, &calldata).map_err(map_call_error)?; + C::abi_decode_returns(&res, false /* validate */) + .map_err(|_| CALL_RETDATA_DECODING_ERROR_MESSAGE.to_vec()) +} From 36f05f4bba89d7cdd345963fd530edf48492df20 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 03:27:16 +0000 Subject: [PATCH 156/160] chore: renamed function for test --- .../ethereum/sdk/stylus/contracts/src/pyth/types.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 3b48109cf6..719a7e8b7c 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -166,7 +166,7 @@ impl StoragePrice { } /// This function is for just for testing - pub fn from_price(price: Price) -> Self { + pub fn test_from_price(price: Price) -> Self { let mut storage_price = unsafe { StoragePrice::new(U256::from(100), 0) }; storage_price.set(price); @@ -200,7 +200,8 @@ impl StoragePriceFeed { self.ema_price.set(price_feed.ema_price); } - pub fn from_price_feed(price_feed: PriceFeed) -> Self { + /// This function is for just for testing + pub fn test_from_price_feed(price_feed: PriceFeed) -> Self { let mut storage_price_feed = unsafe { StoragePriceFeed::new(U256::from(100), 0) }; storage_price_feed.set(price_feed); @@ -277,7 +278,7 @@ mod tests { expo: EXPO, publish_time: U256::from(1000), }; - let storage_price_result = StoragePrice::from_price(price_result); + let storage_price_result = StoragePrice::test_from_price(price_result); let price_result = storage_price_result.to_price(); assert_eq!(price_result.price, PRICE); assert_eq!(price_result.conf, CONF); @@ -305,7 +306,7 @@ mod tests { ema_price: price_result_ema, }; let storage_price_feed_result = - StoragePriceFeed::from_price_feed(price_feed_result); + StoragePriceFeed::test_from_price_feed(price_feed_result); let price_feed_result = storage_price_feed_result.to_price_feed(); assert_eq!(price_feed_result.price.price, PRICE); assert_eq!(price_feed_result.price.conf, CONF); From 658ad518f6f9c7a866e6d5a65e040d428e387e1a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 26 Nov 2024 03:41:56 +0000 Subject: [PATCH 157/160] chore: added some sort of path filters --- target_chains/ethereum/sdk/stylus/.github/workflows/check.yml | 4 ++-- .../ethereum/sdk/stylus/.github/workflows/e2e-tests.yml | 4 ++-- .../ethereum/sdk/stylus/.github/workflows/foundry-test.yml | 4 ++-- .../ethereum/sdk/stylus/.github/workflows/gas-bench.yml | 4 ++-- target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml index 6563489730..dbe4fdd6e9 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/check.yml @@ -14,12 +14,12 @@ permissions: on: pull_request: paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** ** push: branches: - main paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml index 1ed85979a0..d7ba3b1a6f 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/e2e-tests.yml @@ -13,12 +13,12 @@ permissions: on: pull_request: paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** ** push: branches: - main paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml index 661c8e6a01..22b79ce3e0 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/foundry-test.yml @@ -3,12 +3,12 @@ name: foundry-test on: pull_request: paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** ** push: branches: - main paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** env: FOUNDRY_PROFILE: ci diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml index 65784789cb..cdb768bcc6 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/gas-bench.yml @@ -6,12 +6,12 @@ permissions: on: pull_request: paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** ** push: branches: - main paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true diff --git a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml index 7b3d56bfeb..39d30717e8 100644 --- a/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml +++ b/target_chains/ethereum/sdk/stylus/.github/workflows/nostd.yml @@ -7,12 +7,12 @@ permissions: on: pull_request: paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** ** push: branches: - main paths: - - target_chains/sui/contracts/** + - target_chains/ethereum/sdk/stylus/** concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true From dd82af7f9c28cd400323e43170242b8ab1bf366c Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 28 Nov 2024 01:11:56 +0000 Subject: [PATCH 158/160] chore: add cargo fmt and cargo clippy for stylus sdk --- .pre-commit-config.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cf9138762..732d3ce6ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -163,3 +163,15 @@ repos: entry: cargo +1.82.0 clippy --manifest-path ./lazer/Cargo.toml --all-targets -- --deny warnings pass_filenames: false files: lazer + - id: cargo-fmt-stylus-sdk + name: Cargo format for Stylus SDK + language: "rust" + entry: cargo +1.82.0 fmt --manifest-path ./target_chains/ethereum/sdk/stylus/Cargo.toml --all + pass_filenames: false + files: target_chains/ethereum/sdk/stylus + - id: cargo-clippy-stylus-sdk + name: Cargo clippy for Stylus SDK + language: "rust" + entry: cargo +1.82.0 clippy --manifest-path ./target_chains/ethereum/sdk/stylus/Cargo.toml --all-targets -- --deny warnings + pass_filenames: false + files: target_chains/ethereum/sdk/stylus From 8e7c7cc6354acec8758dfea266a9e0725101d17e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 28 Nov 2024 01:28:33 +0000 Subject: [PATCH 159/160] chore: package-json.lock moved depencies to pnpm-workspace.yaml --- pnpm-workspace.yaml | 2 ++ .../pyth-mock-solidity/package-lock.json | 28 ------------------- .../stylus/pyth-mock-solidity/package.json | 6 +--- 3 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1f5361c18e..d4eff6b057 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -19,6 +19,7 @@ packages: - "target_chains/ethereum/entropy_sdk/solidity" - "target_chains/ethereum/sdk/js" - "target_chains/ethereum/sdk/solidity" + - "target_chains/ethereum/sdk/stylus/pyth-mock-solidity" - "target_chains/ethereum/examples/coin_flip/app" - "target_chains/fuel/sdk/js" - "target_chains/starknet/sdk/js" @@ -44,6 +45,7 @@ catalog: "@next/third-parties": 15.0.2 "@phosphor-icons/react": 2.1.7 "@pythnetwork/client": 2.22.0 + "@pythnetwork/pyth-sdk-solidity": 4.0.0 "@react-hookz/web": 24.0.4 "@solana/web3.js": 1.95.4 "@storybook/addon-essentials": 8.3.5 diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json deleted file mode 100644 index 5bc95beaca..0000000000 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package-lock.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "pyth-mock-solidity", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "pyth-mock-solidity", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@pythnetwork/pyth-sdk-solidity": "^4.0.0" - } - }, - "node_modules/@pythnetwork/pyth-sdk-solidity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", - "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" - } - }, - "dependencies": { - "@pythnetwork/pyth-sdk-solidity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-solidity/-/pyth-sdk-solidity-4.0.0.tgz", - "integrity": "sha512-Cy2MvSN1Oh5YpIYmZd2In6/gfXbGjnpazmXKioTuq07Drp4Rl2XHcvtqHdgilplCl32IG4pU+XoRafpexID08A==" - } - } -} diff --git a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json index 93b76ae6db..c21961b5e7 100644 --- a/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json +++ b/target_chains/ethereum/sdk/stylus/pyth-mock-solidity/package.json @@ -2,14 +2,10 @@ "name": "pyth-mock-solidity", "version": "1.0.0", "description": "** Deploy mock solidity pyth contract using the pyth solidity sdk **", - "main": "index.js", "directories": { "lib": "lib", "test": "test" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, + }, "keywords": [], "author": "", "license": "ISC", From 9e34259b86f645b7a30c8f7bd5397360074aadfd Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 28 Nov 2024 01:28:54 +0000 Subject: [PATCH 160/160] chore:test attribute --- target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs index 719a7e8b7c..eb888f5484 100644 --- a/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs +++ b/target_chains/ethereum/sdk/stylus/contracts/src/pyth/types.rs @@ -201,6 +201,7 @@ impl StoragePriceFeed { } /// This function is for just for testing + #[cfg(test)] pub fn test_from_price_feed(price_feed: PriceFeed) -> Self { let mut storage_price_feed = unsafe { StoragePriceFeed::new(U256::from(100), 0) };