diff --git a/target_chains/stylus/Cargo.lock b/target_chains/stylus/Cargo.lock index 1bc6581fd7..adde73d5a9 100644 --- a/target_chains/stylus/Cargo.lock +++ b/target_chains/stylus/Cargo.lock @@ -38,6 +38,29 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -72,7 +95,7 @@ checksum = "69e32ef5c74bbeb1733c37f4ac7f866f8c8af208b7b4265e21af609dcac5bd5e" dependencies = [ "alloy-eips", "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "alloy-serde", "alloy-trie", "auto_impl", @@ -90,7 +113,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "alloy-serde", "serde", ] @@ -102,7 +125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" dependencies = [ "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "crc", "thiserror 2.0.12", ] @@ -114,7 +137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "serde", ] @@ -125,7 +148,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b15b13d38b366d01e818fe8e710d4d702ef7499eacd44926a06171dd9585d0c" dependencies = [ "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", + "k256", "serde", "thiserror 2.0.12", ] @@ -140,7 +164,7 @@ dependencies = [ "alloy-eip2930", "alloy-eip7702", "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "alloy-serde", "auto_impl", "c-kzg", @@ -214,6 +238,23 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-primitives" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f09d3cd21353cf2f41f401e35093390f2a238ce0a72af0f6fc6be5157f525c7" +dependencies = [ + "alloy-rlp 0.2.0", + "bytes", + "const-hex", + "derive_more 0.99.20", + "itoa", + "proptest", + "ruint2", + "serde", + "tiny-keccak", +] + [[package]] name = "alloy-primitives" version = "0.7.6" @@ -236,12 +277,15 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc1360603efdfba91151e623f13a4f4d3dc4af4adc1cbd90bf37c81e84db4c77" dependencies = [ - "alloy-rlp", + "alloy-rlp 0.3.12", + "arbitrary", "bytes", "cfg-if 1.0.0", "const-hex", + "derive_arbitrary", "derive_more 1.0.0", "foldhash", + "getrandom 0.2.16", "hashbrown 0.15.3", "indexmap", "itoa", @@ -249,6 +293,7 @@ dependencies = [ "keccak-asm", "paste", "proptest", + "proptest-derive", "rand 0.8.5", "ruint", "rustc-hash", @@ -294,6 +339,16 @@ dependencies = [ "wasmtimer", ] +[[package]] +name = "alloy-rlp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa8b63682a5fa571b7c0281839e267b6a1f7569ba07b9b57b093050b7c2efda" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "alloy-rlp" version = "0.3.12" @@ -361,7 +416,7 @@ dependencies = [ "alloy-eips", "alloy-network-primitives", "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "alloy-serde", "alloy-sol-types 0.8.20", "itertools 0.14.0", @@ -396,6 +451,22 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "alloy-signer-local" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8f78cd6b7501c7e813a1eb4a087b72d23af51f5bb66d4e948dc840bdd207d8" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives 0.8.20", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.12", +] + [[package]] name = "alloy-sol-macro" version = "0.7.7" @@ -566,7 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" dependencies = [ "alloy-primitives 0.8.20", - "alloy-rlp", + "alloy-rlp 0.3.12", "arrayvec", "derive_more 1.0.0", "nybbles", @@ -625,6 +696,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "ark-ff" version = "0.3.0" @@ -811,6 +888,16 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -879,6 +966,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -954,6 +1050,96 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" +dependencies = [ + "borsh-derive 0.10.4", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" +dependencies = [ + "borsh-derive-internal 0.10.4", + "borsh-schema-derive-internal 0.10.4", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "branches" version = "0.2.1" @@ -985,6 +1171,26 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1428,6 +1634,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1629,6 +1846,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1782,7 +2010,7 @@ dependencies = [ "serde", "serde_json", "syn 2.0.101", - "toml", + "toml 0.8.23", "walkdir", ] @@ -1973,6 +2201,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fast-math" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2465292146cdfc2011350fe3b1c616ac83cf0faeedb33463ba1c332ed8948d66" +dependencies = [ + "ieee754", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2303,6 +2540,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.12", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -2647,6 +2902,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ieee754" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c" + [[package]] name = "impl-codec" version = "0.6.0" @@ -2697,6 +2958,7 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ + "arbitrary", "equivalent", "hashbrown 0.15.3", "serde", @@ -2883,6 +3145,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "libc" @@ -3021,6 +3286,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mock_instant" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" + [[package]] name = "motsu" version = "0.1.0" @@ -3028,12 +3299,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744174e5011fed86212d90c1120037da87e0c45fee7a5861c2b3105e41283810" dependencies = [ "const-hex", - "motsu-proc", + "motsu-proc 0.1.1", "once_cell", "stylus-sdk 0.6.0", "tiny-keccak", ] +[[package]] +name = "motsu" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31eab998c3d9d9e2627e291cc44dc98a8f7c30f63bd21181f6468e58ceb70185" +dependencies = [ + "alloy-primitives 0.8.20", + "alloy-signer-local", + "alloy-sol-types 0.8.20", + "const-hex", + "dashmap", + "k256", + "motsu-proc 0.9.0", + "once_cell", + "revm-precompile", + "stylus-sdk 0.9.0", + "tiny-keccak", +] + [[package]] name = "motsu-proc" version = "0.1.1" @@ -3045,6 +3335,17 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "motsu-proc" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a218b460f9f4f7a22fbfc5ca7d4917c0790a94be431bbc6cdfc2e75263ff2e28" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -3068,6 +3369,20 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3078,6 +3393,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3093,6 +3417,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3128,7 +3474,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.101", @@ -3263,7 +3609,7 @@ version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.101", @@ -3527,6 +3873,15 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -3611,6 +3966,69 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "pyth-receiver-stylus" +version = "0.1.11" +dependencies = [ + "alloy-primitives 0.8.20", + "alloy-sol-types 0.8.20", + "byteorder", + "dotenv", + "ethers", + "eyre", + "hex", + "mock_instant", + "motsu 0.9.1", + "pythnet-sdk", + "serde", + "stylus-sdk 0.9.0", + "tokio", + "wormhole-contract", + "wormhole-vaas", +] + +[[package]] +name = "pyth-sdk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c805ba3dfb5b7ed6a8ffa62ec38391f485a79c7cf6b3b11d3bd44fb0325824" +dependencies = [ + "borsh 0.9.3", + "borsh-derive 0.9.3", + "hex", + "schemars", + "serde", +] + +[[package]] +name = "pythnet-sdk" +version = "2.3.1" +dependencies = [ + "bincode", + "borsh 0.10.4", + "bytemuck", + "byteorder", + "fast-math", + "hex", + "pyth-sdk", + "rustc_version 0.4.1", + "serde", + "sha3", + "slow_primes", + "thiserror 1.0.69", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3862,6 +4280,45 @@ dependencies = [ "web-sys", ] +[[package]] +name = "revm-precompile" +version = "16.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99743c3a2cac341084cc15ac74286c4bf34a0941ebf60aa420cfdb9f81f72f9f" +dependencies = [ + "aurora-engine-modexp", + "blst", + "c-kzg", + "cfg-if 1.0.0", + "k256", + "once_cell", + "revm-primitives", + "ripemd", + "secp256k1", + "sha2", + "substrate-bn", +] + +[[package]] +name = "revm-primitives" +version = "15.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f987564210317706def498421dfba2ae1af64a8edce82c6102758b48133fcb" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives 0.8.20", + "auto_impl", + "bitflags 2.9.1", + "bitvec", + "c-kzg", + "cfg-if 1.0.0", + "dyn-clone", + "enumn", + "hex", + "serde", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -3881,7 +4338,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted 0.7.1", "web-sys", "winapi", @@ -3938,7 +4395,8 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" dependencies = [ - "alloy-rlp", + "alloy-rlp 0.3.12", + "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", @@ -3965,6 +4423,24 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "ruint2" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b066b8e4fcea7fae86b6932d2449670b6b5545b7e8407841b2d3a916ff2a9f86" +dependencies = [ + "derive_more 0.99.20", + "ruint2-macro", + "rustc_version 0.4.1", + "thiserror 1.0.69", +] + +[[package]] +name = "ruint2-macro" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dc553bc0cf4512a8b96caa2e21ed5f6e4b66bf28a1bd08fd9eb07c0b39b28c" + [[package]] name = "rustc-demangle" version = "0.1.25" @@ -3976,6 +4452,9 @@ name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +dependencies = [ + "rand 0.8.5", +] [[package]] name = "rustc-hex" @@ -4123,7 +4602,7 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.101", @@ -4138,6 +4617,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.101", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4180,6 +4683,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -4277,6 +4799,17 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "serde_json" version = "1.0.140" @@ -4401,6 +4934,15 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +[[package]] +name = "slow_primes" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58267dd2fbaa6dceecba9e3e106d2d90a2b02497c0e8b01b8759beccf5113938" +dependencies = [ + "num", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -4440,6 +4982,12 @@ 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" @@ -4536,20 +5084,6 @@ dependencies = [ "dyn-clone", ] -[[package]] -name = "stylus-hello-world" -version = "0.1.11" -dependencies = [ - "alloy-primitives 0.8.20", - "alloy-sol-types 0.8.20", - "dotenv", - "ethers", - "eyre", - "hex", - "stylus-sdk 0.9.0", - "tokio", -] - [[package]] name = "stylus-proc" version = "0.6.1" @@ -4603,7 +5137,6 @@ dependencies = [ "hex", "keccak-const", "lazy_static", - "regex", "stylus-proc 0.6.1", ] @@ -4643,6 +5176,19 @@ dependencies = [ "url", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5010,6 +5556,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.23" @@ -5155,7 +5710,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml", + "toml 0.8.23", ] [[package]] @@ -5666,8 +6221,18 @@ dependencies = [ "base64 0.21.7", "k256", "mini-alloc 0.4.2", - "motsu", - "stylus-sdk 0.6.0", + "motsu 0.1.0", + "stylus-sdk 0.9.0", +] + +[[package]] +name = "wormhole-vaas" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd4775f602f96f911d079c81a12fc964fab8632d56229bbb953335bae043d73" +dependencies = [ + "alloy-primitives 0.2.0", + "hex-literal", ] [[package]] diff --git a/target_chains/stylus/Cargo.toml b/target_chains/stylus/Cargo.toml index c89d2fc067..9ae75bdb28 100644 --- a/target_chains/stylus/Cargo.toml +++ b/target_chains/stylus/Cargo.toml @@ -1,8 +1,7 @@ [workspace] members = [ "contracts/wormhole", - "contracts/pyth-receiver" -] + "contracts/pyth-receiver"] resolver = "2" [workspace.package] @@ -12,7 +11,7 @@ repository = "https://github.com/pyth-network/pyth-crosschain" version = "0.1.0" [workspace.dependencies] -stylus-sdk = { version = "0.6.0", default-features = false } +stylus-sdk = { version = "0.9.0", default-features = false } alloy-primitives = { version = "0.7.6", default-features = false } mini-alloc = { version = "0.4.2", default-features = false } motsu = "0.1.0" diff --git a/target_chains/stylus/contracts/pyth-receiver/Cargo.toml b/target_chains/stylus/contracts/pyth-receiver/Cargo.toml index 0ea3d8955f..8b4921655d 100644 --- a/target_chains/stylus/contracts/pyth-receiver/Cargo.toml +++ b/target_chains/stylus/contracts/pyth-receiver/Cargo.toml @@ -1,18 +1,21 @@ [package] -name = "stylus-hello-world" +name = "pyth-receiver-stylus" version = "0.1.11" 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" +homepage = "https://github.com/pyth-network/pyth-crosschain" +repository = "https://github.com/pyth-network/pyth-crosschain" +keywords = ["arbitrum", "ethereum", "stylus", "alloy", "pyth"] +description = "Pyth receiver contract for the Arbitrum networks, built on Arbitrum Stylus" [dependencies] alloy-primitives = "=0.8.20" alloy-sol-types = "=0.8.20" stylus-sdk = "0.9.0" +byteorder = { version = "1.4.3" } hex = { version = "0.4", default-features = false } +pythnet-sdk = { path = "../../../../pythnet/pythnet_sdk" } +serde = { version = "1.0", features = ["derive"] } +wormhole-vaas = "0.1.1" [dev-dependencies] alloy-primitives = { version = "=0.8.20", features = ["sha3-keccak"] } @@ -21,6 +24,9 @@ ethers = "2.0" eyre = "0.6.8" stylus-sdk = { version = "0.9.0", features = ["stylus-test"] } dotenv = "0.15.0" +motsu = "0.9.0" +wormhole-contract = { path = "../wormhole" } +mock_instant = "0.6.0" [features] default = ["mini-alloc"] @@ -29,7 +35,7 @@ debug = ["stylus-sdk/debug"] mini-alloc = ["stylus-sdk/mini-alloc"] [[bin]] -name = "stylus-hello-world" +name = "pyth-receiver-stylus" path = "src/main.rs" [lib] @@ -37,10 +43,8 @@ crate-type = ["lib", "cdylib"] [profile.release] codegen-units = 1 -strip = true +opt-level = "s" lto = true +debug = false panic = "abort" - -# If you need to reduce the binary size, it is advisable to try other -# optimization levels, such as "s" and "z" -opt-level = 3 +overflow-checks = true \ No newline at end of file diff --git a/target_chains/stylus/contracts/pyth-receiver/src/error.rs b/target_chains/stylus/contracts/pyth-receiver/src/error.rs index 6b44614f9b..b20cb5284d 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/error.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/error.rs @@ -1,7 +1,22 @@ use alloc::vec::Vec; +#[derive(PartialEq)] pub enum PythReceiverError { - PriceUnavailable + PriceUnavailable, + InvalidUpdateData, + VaaVerificationFailed, + InvalidVaa, + InvalidWormholeMessage, + InvalidMerkleProof, + InvalidAccumulatorMessage, + InvalidMerkleRoot, + InvalidMerklePath, + InvalidUnknownSource, + NewPriceUnavailable, + InvalidAccumulatorMessageType, + InsufficientFee, + InvalidEmitterAddress, + TooManyUpdates, } impl core::fmt::Debug for PythReceiverError { @@ -14,6 +29,20 @@ impl From for Vec { fn from(error: PythReceiverError) -> Vec { vec![match error { PythReceiverError::PriceUnavailable => 1, + PythReceiverError::InvalidUpdateData => 2, + PythReceiverError::VaaVerificationFailed => 3, + PythReceiverError::InvalidVaa => 4, + PythReceiverError::InvalidWormholeMessage => 5, + PythReceiverError::InvalidMerkleProof => 6, + PythReceiverError::InvalidAccumulatorMessage => 7, + PythReceiverError::InvalidMerkleRoot => 8, + PythReceiverError::InvalidMerklePath => 9, + PythReceiverError::InvalidUnknownSource => 10, + PythReceiverError::NewPriceUnavailable => 11, + PythReceiverError::InvalidAccumulatorMessageType => 12, + PythReceiverError::InsufficientFee => 13, + PythReceiverError::InvalidEmitterAddress => 14, + PythReceiverError::TooManyUpdates => 15, }] } -} \ No newline at end of file +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs new file mode 100644 index 0000000000..0d5cb57d0f --- /dev/null +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -0,0 +1,345 @@ +#[cfg(test)] +mod test { + use crate::error::PythReceiverError; + use crate::test_data::*; + use crate::PythReceiver; + use alloy_primitives::{Address, U256}; + use mock_instant::global::MockClock; + use motsu::prelude::*; + use pythnet_sdk::wire::v1::{AccumulatorUpdateData, Proof}; + use std::time::Duration; + use wormhole_contract::WormholeContract; + const TEST_PRICE_ID: [u8; 32] = [ + 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, + 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, + 0x5b, 0x43, + ]; + + const PYTHNET_CHAIN_ID: u16 = 26; + const PYTHNET_EMITTER_ADDRESS: [u8; 32] = [ + 0xe1, 0x01, 0xfa, 0xed, 0xac, 0x58, 0x51, 0xe3, 0x2b, 0x9b, 0x23, 0xb5, 0xf9, 0x41, 0x1a, + 0x8c, 0x2b, 0xac, 0x4a, 0xae, 0x3e, 0xd4, 0xdd, 0x7b, 0x81, 0x1d, 0xd1, 0xa7, 0x2e, 0xa4, + 0xaa, 0x71, + ]; + + const CHAIN_ID: u16 = 60051; + const GOVERNANCE_CHAIN_ID: u16 = 1; + const GOVERNANCE_CONTRACT: U256 = U256::from_limbs([4, 0, 0, 0]); + + const SINGLE_UPDATE_FEE_IN_WEI: U256 = U256::from_limbs([100, 0, 0, 0]); + const TRANSACTION_FEE_IN_WEI: U256 = U256::from_limbs([32, 0, 0, 0]); + + #[cfg(test)] + fn mock_get_update_fee(update_data: Vec>) -> Result { + let mut total_num_updates: u64 = 0; + for data in &update_data { + let update_data_array: &[u8] = &data; + let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + match accumulator_update.proof { + Proof::WormholeMerkle { vaa: _, updates } => { + let num_updates = u64::try_from(updates.len()) + .map_err(|_| PythReceiverError::TooManyUpdates)?; + total_num_updates += num_updates; + } + } + } + Ok(get_total_fee(total_num_updates)) + } + + fn get_total_fee(total_num_updates: u64) -> U256 { + U256::from(total_num_updates).saturating_mul(SINGLE_UPDATE_FEE_IN_WEI) + + TRANSACTION_FEE_IN_WEI + } + + #[cfg(test)] + fn pyth_wormhole_init( + pyth_contract: &Contract, + wormhole_contract: &Contract, + alice: &Address, + ) { + let guardians = current_guardians(); + let governance_contract = + Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); + wormhole_contract + .sender(*alice) + .initialize( + guardians, + 4, + CHAIN_ID, + GOVERNANCE_CHAIN_ID, + governance_contract, + ) + .unwrap(); + + let single_update_fee = SINGLE_UPDATE_FEE_IN_WEI; + let valid_time_period = U256::from(3600u64); + + let data_source_chain_ids = vec![PYTHNET_CHAIN_ID]; + let data_source_emitter_addresses = vec![PYTHNET_EMITTER_ADDRESS]; + + let governance_chain_id = 1u16; + let governance_emitter_address = [3u8; 32]; + let governance_initial_sequence = 0u64; + + pyth_contract.sender(*alice).initialize( + wormhole_contract.address(), + single_update_fee, + valid_time_period, + data_source_chain_ids, + data_source_emitter_addresses, + governance_chain_id, + governance_emitter_address, + governance_initial_sequence, + ); + } + + #[motsu::test] + fn tests_pyth_end_to_end_with_update( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = good_update1(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + assert!(price_result.is_ok()); + assert_eq!(price_result.unwrap(), good_update1_results()); + } + + #[motsu::test] + fn test_update_price_feed_reverts_insufficient_fee( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + alice.fund(U256::from(200)); + + let update_data = good_update1(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + let small_update_fee = update_fee / U256::from(2); + + let result = pyth_contract + .sender_and_value(alice, small_update_fee) + .update_price_feeds(update_data); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), PythReceiverError::InsufficientFee); + } + + #[motsu::test] + fn test_get_price_after_multiple_different_updates_returns_recent_price( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data1 = good_update1(); + let update_fee1 = mock_get_update_fee(update_data1.clone()).unwrap(); + + let update_data2 = good_update2(); + let update_fee2 = mock_get_update_fee(update_data2.clone()).unwrap(); + + alice.fund(update_fee1 + update_fee2); + + let result1 = pyth_contract + .sender_and_value(alice, update_fee1) + .update_price_feeds(update_data1); + assert!(result1.is_ok()); + + let result2 = pyth_contract + .sender_and_value(alice, update_fee2) + .update_price_feeds(update_data2); + assert!(result2.is_ok()); + + let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + assert!(price_result.is_ok()); + assert_eq!(price_result.unwrap(), good_update2_results()); + } + + #[motsu::test] + fn test_get_price_with_no_update_reverts_with_price_unavailable( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + assert!(price_result.is_err()); + assert_eq!( + price_result.unwrap_err(), + PythReceiverError::PriceUnavailable + ); + } + + #[motsu::test] + fn test_get_price_no_older_than_with_random_id_reverts_with_price_unavailable( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + MockClock::set_time(Duration::from_secs(1761573860)); // less than good_update2().timestamp + 1s + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let random_id: [u8; 32] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, + 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + ]; + + let price_result = pyth_contract + .sender(alice) + .get_price_no_older_than(random_id, 3600); + assert!(price_result.is_err()); + assert_eq!( + price_result.unwrap_err(), + PythReceiverError::PriceUnavailable + ); + } + + #[motsu::test] + fn test_get_price_no_older_than_where_update_younger_than_max_age_returns_price( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + MockClock::set_time(Duration::from_secs(1761573860)); + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = good_update2(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let price_result = pyth_contract + .sender(alice) + .get_price_no_older_than(TEST_PRICE_ID, u64::MAX); + assert!(price_result.is_ok()); + assert_eq!(price_result.unwrap(), good_update2_results()); + } + + #[motsu::test] + fn test_get_price_no_older_than_reverts_too_old( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + MockClock::set_time(Duration::from_secs(1761573860)); + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = good_update2(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let price_result = pyth_contract + .sender(alice) + .get_price_no_older_than(TEST_PRICE_ID, 1); + assert!(price_result.is_err()); + assert_eq!( + price_result.unwrap_err(), + PythReceiverError::NewPriceUnavailable + ); + } + + #[motsu::test] + fn test_multiple_updates_in_same_vaa_different_ids_updates_both( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = multiple_updates_same_vaa(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let first_id: [u8; 32] = [ + 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, + 0xe5, 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, + 0x4a, 0x41, 0x5b, 0x43, + ]; + let second_id: [u8; 32] = [ + 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, + 0x64, 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, + 0x34, 0xfd, 0x0a, 0xce, + ]; + + let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); + assert!(first_price_result.is_ok()); + assert_eq!(first_price_result.unwrap(), multiple_updates_results()[0]); + + let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); + assert!(second_price_result.is_ok()); + assert_eq!(second_price_result.unwrap(), multiple_updates_results()[1]); + } + + #[motsu::test] + fn test_multiple_updates_different_ids_updates_both( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = multiple_updates_diff_vaa(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let first_id: [u8; 32] = [ + 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, + 0x62, 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, + 0x9c, 0x1a, 0xdc, 0xf5, + ]; + let second_id: [u8; 32] = TEST_PRICE_ID; + + let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); + assert!(first_price_result.is_ok()); + assert_eq!( + first_price_result.unwrap(), + multiple_updates_diff_vaa_results()[0] + ); + + let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); + assert!(second_price_result.is_ok()); + assert_eq!( + second_price_result.unwrap(), + multiple_updates_diff_vaa_results()[1] + ); + } +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 35fc43c127..aab2d43a60 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -5,26 +5,62 @@ #[macro_use] extern crate alloc; -mod structs; mod error; +#[cfg(test)] +mod integration_tests; +mod structs; +#[cfg(test)] +mod test_data; + +#[cfg(test)] +use mock_instant::global::MockClock; use alloc::vec::Vec; -use stylus_sdk::{alloy_primitives::{U256, U64, I32, I64, FixedBytes}, - prelude::*, - storage::{StorageAddress, StorageVec, StorageMap, StorageUint, StorageBool, StorageU256}}; +use stylus_sdk::{ + alloy_primitives::{Address, FixedBytes, I32, I64, U16, U256, U32, U64}, + call::Call, + prelude::*, + storage::{ + StorageAddress, StorageBool, StorageFixedBytes, StorageMap, StorageU16, StorageU256, + StorageUint, StorageVec, + }, +}; -use structs::{DataSourceStorage, PriceInfoReturn, PriceInfoStorage}; -use error::{PythReceiverError}; +use error::PythReceiverError; +use pythnet_sdk::{ + accumulators::merkle::{MerklePath, MerkleRoot}, + hashers::keccak256_160::Keccak160, + messages::Message, + wire::{ + from_slice, + v1::{ + AccumulatorUpdateData, Proof, WormholeMessage, WormholePayload, + PYTHNET_ACCUMULATOR_UPDATE_MAGIC, + }, + }, +}; +use structs::{DataSource, DataSourceStorage, PriceInfoReturn, PriceInfoStorage}; +use wormhole_vaas::{Readable, Vaa, Writeable}; + +sol_interface! { + interface IWormholeContract { + function initialize(address[] memory initial_guardians, uint16 chain_id, uint16 governance_chain_id, address governance_contract) external; + function getGuardianSet(uint32 index) external view returns (uint8[] memory); + function parseAndVerifyVm(uint8[] memory encoded_vaa) external view returns (uint8[] memory); + function quorum(uint32 num_guardians) external pure returns (uint32); + } +} #[storage] #[entrypoint] pub struct PythReceiver { pub wormhole: StorageAddress, pub valid_data_sources: StorageVec, - pub is_valid_data_source: StorageMap, StorageBool>, + pub is_valid_data_source: StorageMap, pub single_update_fee_in_wei: StorageU256, pub valid_time_period_seconds: StorageU256, - pub governance_data_source: DataSourceStorage, + pub governance_data_source_chain_id: StorageU16, + pub governance_data_source_emitter_address: StorageFixedBytes<32>, pub last_executed_governance_sequence: StorageUint<64, 1>, pub governance_data_source_index: StorageUint<32, 1>, pub latest_price_info: StorageMap, PriceInfoStorage>, @@ -33,11 +69,53 @@ pub struct PythReceiver { #[public] impl PythReceiver { - pub fn get_price_unsafe(&self, _id: [u8; 32]) -> Result { - let id_fb = FixedBytes::<32>::from(_id); - + pub fn initialize( + &mut self, + wormhole: Address, + single_update_fee_in_wei: U256, + valid_time_period_seconds: U256, + data_source_emitter_chain_ids: Vec, + data_source_emitter_addresses: Vec<[u8; 32]>, + governance_emitter_chain_id: u16, + governance_emitter_address: [u8; 32], + governance_initial_sequence: u64, + ) { + self.wormhole.set(wormhole); + self.single_update_fee_in_wei.set(single_update_fee_in_wei); + self.valid_time_period_seconds + .set(valid_time_period_seconds); + + self.governance_data_source_chain_id + .set(U16::from(governance_emitter_chain_id)); + self.governance_data_source_emitter_address + .set(FixedBytes::<32>::from(governance_emitter_address)); + + self.last_executed_governance_sequence + .set(U64::from(governance_initial_sequence)); + self.governance_data_source_index.set(U32::ZERO); + + for (i, chain_id) in data_source_emitter_chain_ids.iter().enumerate() { + let emitter_address = FixedBytes::<32>::from(data_source_emitter_addresses[i]); + + // Create a new data source storage slot + let mut data_source = self.valid_data_sources.grow(); + data_source.chain_id.set(U16::from(*chain_id)); + data_source.emitter_address.set(emitter_address); + + let data_source_key = DataSource { + chain_id: U16::from(*chain_id), + emitter_address: emitter_address, + }; + + self.is_valid_data_source.setter(data_source_key).set(true); + } + } + + pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { + let id_fb = FixedBytes::<32>::from(id); + let price_info = self.latest_price_info.get(id_fb); - + if price_info.publish_time.get() == U64::ZERO { return Err(PythReceiverError::PriceUnavailable); } @@ -52,24 +130,65 @@ impl PythReceiver { )) } - pub fn get_price_no_older_than(&self, _id: [u8; 32], _age: u64) -> Result { - let price_info = self.get_price_unsafe(_id)?; - if !self.is_no_older_than(price_info.0, _age) { - return Err(PythReceiverError::PriceUnavailable); + pub fn get_price_no_older_than( + &self, + id: [u8; 32], + age: u64, + ) -> Result { + let price_info = self.get_price_unsafe(id)?; + if !self.is_no_older_than(price_info.0, age) { + return Err(PythReceiverError::NewPriceUnavailable); } Ok(price_info) } - pub fn get_ema_price_unsafe(&self, _id: [u8; 32]) -> PriceInfoReturn { - (U64::ZERO, I32::ZERO, I64::ZERO, U64::ZERO, I64::ZERO, U64::ZERO) + pub fn get_ema_price_unsafe(&self, id: [u8; 32]) -> Result { + let id_fb = FixedBytes::<32>::from(id); + let price_info = self.latest_price_info.get(id_fb); + + if price_info.publish_time.get() == U64::ZERO { + return Err(PythReceiverError::PriceUnavailable); + } + + Ok(( + price_info.publish_time.get(), + price_info.expo.get(), + price_info.ema_price.get(), + price_info.ema_conf.get(), + price_info.ema_price.get(), + price_info.ema_conf.get(), + )) } - pub fn get_ema_price_no_older_than(&self, _id: [u8; 32], _age: u64) -> PriceInfoReturn { - (U64::ZERO, I32::ZERO, I64::ZERO, U64::ZERO, I64::ZERO, U64::ZERO) + pub fn get_ema_price_no_older_than( + &self, + id: [u8; 32], + age: u64, + ) -> Result { + let price_info = self.get_ema_price_unsafe(id)?; + if !self.is_no_older_than(price_info.0, age) { + return Err(PythReceiverError::NewPriceUnavailable); + } + Ok(price_info) } - pub fn update_price_feeds(&mut self, _update_data: Vec>) { - // dummy implementation + #[payable] + pub fn update_price_feeds( + &mut self, + update_data: Vec>, + ) -> Result<(), PythReceiverError> { + for data in &update_data { + self.update_price_feeds_internal(data.clone())?; + } + + let total_fee = self.get_update_fee(update_data)?; + + let value = self.vm().msg_value(); + + if value < total_fee { + return Err(PythReceiverError::InsufficientFee); + } + Ok(()) } pub fn update_price_feeds_if_necessary( @@ -81,8 +200,127 @@ impl PythReceiver { // dummy implementation } - pub fn get_update_fee(&self, _update_data: Vec>) -> U256 { - U256::from(0u8) + fn update_price_feeds_internal( + &mut self, + update_data: Vec, + ) -> Result<(), PythReceiverError> { + let update_data_array: &[u8] = &update_data; + // Check the first 4 bytes of the update_data_array for the magic header + if update_data_array.len() < 4 { + return Err(PythReceiverError::InvalidUpdateData); + } + + let mut header = [0u8; 4]; + header.copy_from_slice(&update_data_array[0..4]); + + if &header != PYTHNET_ACCUMULATOR_UPDATE_MAGIC { + return Err(PythReceiverError::InvalidAccumulatorMessage); + } + + let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + + match accumulator_update.proof { + Proof::WormholeMerkle { vaa, updates } => { + let wormhole: IWormholeContract = IWormholeContract::new(self.wormhole.get()); + let config = Call::new(); + wormhole + .parse_and_verify_vm(config, Vec::from(vaa.clone())) + .map_err(|_| PythReceiverError::InvalidWormholeMessage)?; + + let vaa = Vaa::read(&mut Vec::from(vaa.clone()).as_slice()) + .map_err(|_| PythReceiverError::VaaVerificationFailed)?; + + let cur_emitter_address: &[u8; 32] = vaa + .body + .emitter_address + .as_slice() + .try_into() + .map_err(|_| PythReceiverError::InvalidEmitterAddress)?; + + let cur_data_source = DataSource { + chain_id: U16::from(vaa.body.emitter_chain), + emitter_address: FixedBytes::from(cur_emitter_address), + }; + + if !self.is_valid_data_source.get(cur_data_source) { + return Err(PythReceiverError::InvalidWormholeMessage); + } + + let root_digest: MerkleRoot = parse_wormhole_proof(vaa)?; + + for update in updates { + let message_vec = Vec::from(update.message); + let proof: MerklePath = update.proof; + + if !root_digest.check(proof, &message_vec) { + return Err(PythReceiverError::InvalidMerkleProof); + } + + let msg = from_slice::(&message_vec) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + + match msg { + Message::PriceFeedMessage(price_feed_message) => { + let price_id_fb: FixedBytes<32> = + FixedBytes::from(price_feed_message.feed_id); + let mut recent_price_info = self.latest_price_info.setter(price_id_fb); + + if recent_price_info.publish_time.get() + < U64::from(price_feed_message.publish_time) + || recent_price_info.price.get() == I64::ZERO + { + recent_price_info + .publish_time + .set(U64::from(price_feed_message.publish_time)); + recent_price_info.price.set(I64::from_le_bytes( + price_feed_message.price.to_le_bytes(), + )); + recent_price_info + .conf + .set(U64::from(price_feed_message.conf)); + recent_price_info.expo.set(I32::from_le_bytes( + price_feed_message.exponent.to_le_bytes(), + )); + recent_price_info.ema_price.set(I64::from_le_bytes( + price_feed_message.ema_price.to_le_bytes(), + )); + recent_price_info + .ema_conf + .set(U64::from(price_feed_message.ema_conf)); + } + } + _ => { + return Err(PythReceiverError::InvalidAccumulatorMessageType); + } + } + } + } + }; + + Ok(()) + } + + fn get_update_fee(&self, update_data: Vec>) -> Result { + let mut total_num_updates: u64 = 0; + for data in &update_data { + let update_data_array: &[u8] = &data; + let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + match accumulator_update.proof { + Proof::WormholeMerkle { vaa: _, updates } => { + let num_updates = u64::try_from(updates.len()) + .map_err(|_| PythReceiverError::TooManyUpdates)?; + total_num_updates += num_updates; + } + } + } + Ok(self.get_total_fee(total_num_updates)) + } + + fn get_total_fee(&self, total_num_updates: u64) -> U256 { + U256::from(total_num_updates).saturating_mul(self.single_update_fee_in_wei.get()) + + self.transaction_fee_in_wei.get() } pub fn get_twap_update_fee(&self, _update_data: Vec>) -> U256 { @@ -131,9 +369,30 @@ impl PythReceiver { } fn is_no_older_than(&self, publish_time: U64, max_age: u64) -> bool { - let current_u64: u64 = self.vm().block_timestamp(); - let publish_time_u64: u64 = publish_time.to::(); - - current_u64.saturating_sub(publish_time_u64) <= max_age + self.get_current_timestamp() + .saturating_sub(publish_time.to::()) + <= max_age } + + // Stylus doesn't provide a way to mock up the testing timestamp + // so at the moment I'm using the testing trait to let me test old timestamps + fn get_current_timestamp(&self) -> u64 { + #[cfg(test)] + { + MockClock::time().as_secs() + } + #[cfg(not(test))] + { + self.vm().block_timestamp() + } + } +} + +fn parse_wormhole_proof(vaa: Vaa) -> Result, PythReceiverError> { + let message = WormholeMessage::try_from_bytes(vaa.body.payload.to_vec()) + .map_err(|_| PythReceiverError::PriceUnavailable)?; + let root: MerkleRoot = MerkleRoot::new(match message.payload { + WormholePayload::Merkle(merkle_root) => merkle_root.root, + }); + Ok(root) } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index edb1b2b5cf..13e419ca15 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -1,15 +1,40 @@ use alloc::vec::Vec; -use stylus_sdk::{prelude::*, storage::{StorageU16, StorageU64, StorageI32, StorageI64, StorageFixedBytes}}; -use stylus_sdk::alloy_primitives::{U64, I32, I64}; +use stylus_sdk::alloy_primitives::{keccak256, FixedBytes, B256, I32, I64, U16, U256, U64}; +use stylus_sdk::{ + prelude::*, + storage::{StorageFixedBytes, StorageI32, StorageI64, StorageKey, StorageU16, StorageU64}, +}; -// DataSource struct to store chain/emitter pairs +fn serialize_data_source_to_bytes(chain_id: u16, emitter_address: &[u8; 32]) -> [u8; 34] { + let mut result = [0u8; 34]; + result[0..2].copy_from_slice(&chain_id.to_be_bytes()); + result[2..].copy_from_slice(emitter_address); + result +} + +#[derive(Debug)] #[storage] pub struct DataSourceStorage { pub chain_id: StorageU16, pub emitter_address: StorageFixedBytes<32>, } -// PriceInfo struct storing price information +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DataSource { + pub chain_id: U16, + pub emitter_address: FixedBytes<32>, +} + +impl StorageKey for DataSource { + fn to_slot(&self, root: B256) -> U256 { + let chain_id: u16 = self.chain_id.to::(); + let emitter_address: [u8; 32] = self.emitter_address.as_slice().try_into().unwrap(); + + let bytes = serialize_data_source_to_bytes(chain_id, &emitter_address); + + keccak256(bytes).to_slot(root) + } +} #[storage] pub struct PriceInfoStorage { pub publish_time: StorageU64, @@ -20,5 +45,44 @@ pub struct PriceInfoStorage { pub ema_conf: StorageU64, } -// PriceInfo struct storing price information -pub type PriceInfoReturn = (U64, I32, I64, U64, I64, U64); \ No newline at end of file +// Addressing nit -- running into some versioning issues that preclude me +// from returning the PriceInfo struct directly. Need to figure that out. + +// pub struct PriceInfo { +// pub publish_time: U64, +// pub expo: I32, +// pub price: I64, +// pub conf: U64, +// pub ema_price: I64, +// pub ema_conf: U64, +// } + +pub type PriceInfoReturn = (U64, I32, I64, U64, I64, U64); + +#[cfg(test)] +mod tests { + use super::*; + use stylus_sdk::alloy_primitives::{FixedBytes, U16}; + + #[test] + fn test_data_source_serialization_compatibility() { + let chain_id = 1u16; + let emitter_address = [1u8; 32]; + + let _data_source = DataSource { + chain_id: U16::from(chain_id), + emitter_address: FixedBytes::from(emitter_address), + }; + + let mut expected_bytes = [0u8; 34]; + expected_bytes[0..2].copy_from_slice(&chain_id.to_be_bytes()); + expected_bytes[2..].copy_from_slice(&emitter_address); + + let actual_bytes = serialize_data_source_to_bytes(chain_id, &emitter_address); + + assert_eq!( + actual_bytes, expected_bytes, + "Serialization should produce identical bytes" + ); + } +} diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs new file mode 100644 index 0000000000..e27298a206 --- /dev/null +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -0,0 +1,127 @@ +use alloy_primitives::{address, Address, I32, I64, U64}; +use hex::FromHex; +use std::vec; + +#[cfg(test)] +pub fn good_update1() -> Vec> { + let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] +} + +#[cfg(test)] +pub fn good_update2() -> Vec> { + let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] +} + +#[cfg(test)] +pub fn multiple_updates_same_vaa() -> Vec> { + let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] +} + +#[cfg(test)] +pub fn multiple_updates_diff_vaa() -> Vec> { + let hex_str_update_1 = "504e41550100000003b801000000040d0168bad50bb1470e136368ebadeb842cf5256547a30cd65c320a0e476bca15f204326806e4290c004925040f5eea31592c55341936fe7e7537d7b4172f308fc79701033b17478509bf096ebc6107afaaf9f339f68d3ad7ebd9267585cd945b91c93f687550c07d4526dc6f2ad0335c1e849977d91d2e61be04d746a611b444af272a9f0104ce96c5a8d9e4e869c2b7ae54980bf9f315af3d93fe827a346b3b55041e019e982350498c7094c77f09567cd3f2a9961940e304666fb08bfbfac7aa1a6ec157cf010639036dea5439380e1c946f76f06480965b60edf280272ab7f649840e289e3d740e4822619e5608811c66158d22ccdea633961687b3afec2751a53a37b3fd6f4a010a370bf667e0fd0b6df27ae87e94513740d1581ca9eadc15b049b5ce4d96b57ba85f92a7bdc478fde36efd7ab80674048589421fc2644457d7b4e398bc1b5c01f0010bb5ef6559a20289cf9df4a8c076baecd3d8e3567973acf975ee52d0301b8cbddd7bb2e4348fb59020bae2caefd57945cf1107cff7f5fd068079fa59ea27e0e702000cae84bce54e40a0583d8c957ddab0388d053a85622cd83c3de49e4bc4acf515351e118f4c36b903f09fe3afd7ce5499c73c37e80d742fdfbb1a57d7f442f915f7000d90c05b57e9592b303305b0e2a88b05f5b006a5f911ac9feba74c309acbfc3af8270b4d8c0759677db601af1893ec4523f023ee6810070148cd017ca2fe2d5663010e17a7472d37425d9f1505e998736d4138f6f35c620c195340ef87ca0d155cf3aa12fe9c0a9c6341411734dacbc53e36895efa69c9895d682bc6329294a49fd928000fed2cb761a8837d87cf827141eedfeafd1901e38957930bf56cc044ebf2c34e3e06bd8197b10d356236acb2c31be6031b95caf2d7492e2744b176bf8a4a357cb70110f22c1178f5c734e83134432c2f5c76cb6462ccba9db0177640cde08b5413ce09630a9c0d5682ee2a1acca1f5d0a6a3583b9578c6e0fbb2d489e252ec7e5960a90011d984560f66ce62be4ba2512e2d62777429f37d6020f9dcb9a0ac54522f80462b4ff33ab5c832748e50cda01e34eef28271982ac22b216cac2a6bde33a83f6bfd011207131b4a1ab2f2ed984cd444210c5c4147d5da72b27d1fdbf6f1a087141ab6971b20175fd67abc937ad69a42991b780804e67e926a8ca2b218521d6572c394c100686ed88a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000894a31a014155575600000000000da2316b00002710d957df1a464d0193bc0c9dd3b555d0544a04623e010055003fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf50000000002181de10000000000008db7fffffff800000000686ed88a00000000686ed88a00000000021501d40000000000008b300c29ff39535a48e8f09021a1fee7b9e887d68522dd790970609044b11d98aa7d4beb0d1e390c89ccad2fda83fd5316ad4483fc398b6534be617d42690cc27297ccbf19315c79064275612a0d16a2536fdafaca0648bd28b54a014a468906203c8a9d3deadd4ec12dd380bdd27f9a9bb41a5e841d23671743da3125f40b479518be5250080ac78df8826fb59d02dd07b310b69f954dea12b87cb5aa66af75d3da3f7327578f8ed0c0a7b98d1becad74b16e03ff52eee04497971d9985fd9cc05215490b44499df2c3a64f6fefa743421d947d34de37e185b242db065c8f89e9bb028a3710646273f0721e26c68dbd85fb20"; + let hex_str_update_2 = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; + let bytes_1 = Vec::from_hex(hex_str_update_1).expect("Invalid hex string"); + let bytes_2 = Vec::from_hex(hex_str_update_2).expect("Invalid hex string"); + + vec![bytes_1, bytes_2] +} + +#[cfg(test)] +pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2] { + [ + ( + U64::from(1752094858u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(35134945i64.to_le_bytes()), + U64::from(36279u64), + I64::from_le_bytes(34931156i64.to_le_bytes()), + U64::from(35632u64), + ), + ( + U64::from(1751573860u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10985663592646i64.to_le_bytes()), + U64::from(4569386330u64), + I64::from_le_bytes(10977795800000i64.to_le_bytes()), + U64::from(3919318300u64), + ), + ] +} + +#[cfg(test)] +pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { + ( + U64::from(1751563000u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10967241867779i64.to_le_bytes()), + U64::from(4971244966u64), + I64::from_le_bytes(10942391100000i64.to_le_bytes()), + U64::from(4398561400u64), + ) +} + +#[cfg(test)] +pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { + [ + ( + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10990356724259i64.to_le_bytes()), + U64::from(3891724259u64), + I64::from_le_bytes(10974970400000i64.to_le_bytes()), + U64::from(3918344000u64), + ), + ( + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(258906787480i64.to_le_bytes()), + U64::from(158498649u64), + I64::from_le_bytes(258597182000i64.to_le_bytes()), + U64::from(131285914u64), + ), + ] +} + +#[cfg(test)] +pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { + ( + U64::from(1751573860u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10985663592646i64.to_le_bytes()), + U64::from(4569386330u64), + I64::from_le_bytes(10977795800000i64.to_le_bytes()), + U64::from(3919318300u64), + ) +} + +#[cfg(test)] +pub fn current_guardians() -> Vec
{ + vec![ + address!("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3"), // Rockaway + address!("0xfF6CB952589BDE862c25Ef4392132fb9D4A42157"), // Staked + address!("0x114De8460193bdf3A2fCf81f86a09765F4762fD1"), // Figment + address!("0x107A0086b32d7A0977926A205131d8731D39cbEB"), // ChainodeTech + address!("0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2"), // Inotel + address!("0x11b39756C042441BE6D8650b69b54EbE715E2343"), // HashKey Cloud + address!("0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd"), // ChainLayer + address!("0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20"), // xLabs + address!("0x74a3bf913953D695260D88BC1aA25A4eeE363ef0"), // Forbole + address!("0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e"), // Staking Fund + address!("0xAF45Ced136b9D9e24903464AE889F5C8a723FC14"), // Moonlet Wallet + address!("0xf93124b7c738843CBB89E864c862c38cddCccF95"), // P2P Validator + address!("0xD2CC37A4dc036a8D232b48f62cDD4731412f4890"), // 01node + address!("0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811"), // MCF + address!("0x71AA1BE1D36CaFE3867910F99C09e347899C19C3"), // Everstake + address!("0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf"), // Chorus One + address!("0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8"), // Syncnode + address!("0x5E1487F35515d02A92753504a8D75471b9f49EdB"), // Triton + address!("0x6FbEBc898F403E4773E95feB15E80C9A99c8348d"), // Staking Facilities + ] +} diff --git a/target_chains/stylus/contracts/wormhole/Cargo.toml b/target_chains/stylus/contracts/wormhole/Cargo.toml index bccb6bcce5..74bdbac554 100644 --- a/target_chains/stylus/contracts/wormhole/Cargo.toml +++ b/target_chains/stylus/contracts/wormhole/Cargo.toml @@ -23,4 +23,10 @@ motsu.workspace = true base64 = "0.21" [lib] +name = "wormhole_contract" crate-type = ["lib", "cdylib"] + +[[bin]] +name = "wormhole-contract" +path = "src/main.rs" +required-features = ["export-abi"] diff --git a/target_chains/stylus/contracts/wormhole/src/lib.rs b/target_chains/stylus/contracts/wormhole/src/lib.rs index 1f16069010..5b0aea296d 100644 --- a/target_chains/stylus/contracts/wormhole/src/lib.rs +++ b/target_chains/stylus/contracts/wormhole/src/lib.rs @@ -7,15 +7,9 @@ extern crate alloc; #[global_allocator] static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; -#[cfg(not(any(feature = "std", feature = "export-abi")))] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - use alloc::{vec, vec::Vec}; use stylus_sdk::{ - prelude::{entrypoint, public, storage}, + prelude::*, storage::{StorageMap, StorageUint, StorageAddress, StorageBool}, alloy_primitives::{Address, FixedBytes, U256, keccak256}, }; @@ -320,7 +314,6 @@ impl WormholeContract { fn verify_vm(&self, vaa: &VerifiedVM) -> Result<(), WormholeError> { - let guardian_set = self.get_gs_internal(vaa.guardian_set_index)?; if vaa.guardian_set_index != self.current_guardian_set_index.get().try_into().unwrap_or(0u32) && guardian_set.expiration_time > 0 { diff --git a/target_chains/stylus/contracts/wormhole/src/main.rs b/target_chains/stylus/contracts/wormhole/src/main.rs index db12868da2..f3ff3e41c1 100644 --- a/target_chains/stylus/contracts/wormhole/src/main.rs +++ b/target_chains/stylus/contracts/wormhole/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "export-abi"), no_main)] +#![cfg_attr(not(feature = "export-abi"), no_std)] #[cfg(feature = "export-abi")] fn main() {