diff --git a/Cargo.lock b/Cargo.lock index 592ebb3fb..da69468a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -118,43 +118,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.88" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "ark-ec" @@ -169,7 +169,7 @@ dependencies = [ "derivative", "hashbrown 0.13.2", "itertools 0.10.5", - "num-traits 0.2.19", + "num-traits", "zeroize", ] @@ -187,7 +187,7 @@ dependencies = [ "digest", "itertools 0.10.5", "num-bigint", - "num-traits 0.2.19", + "num-traits", "paste", "rustc_version", "zeroize", @@ -210,7 +210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", - "num-traits 0.2.19", + "num-traits", "proc-macro2", "quote", "syn 1.0.109", @@ -280,7 +280,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ - "num-traits 0.2.19", + "num-traits", "rand", ] @@ -307,9 +307,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-compression" -version = "0.4.12" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "brotli", "flate2", @@ -323,13 +323,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -368,29 +368,30 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[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 = "axum" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", + "base64 0.22.1", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.2", "hyper-util", "itoa", "matchit", @@ -403,9 +404,11 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sha1", + "sync_wrapper 1.0.2", "tokio", - "tower", + "tokio-tungstenite 0.24.0", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -413,20 +416,20 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -441,7 +444,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.0", + "miniz_oxide 0.8.2", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -485,15 +488,15 @@ checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -570,15 +573,15 @@ dependencies = [ "cairo-lang-starknet-classes", "cairo-lang-utils 2.7.0", "cairo-vm", - "derive_more", - "indexmap 2.5.0", + "derive_more 0.99.18", + "indexmap 2.7.0", "itertools 0.10.5", "keccak", "log", "num-bigint", "num-integer", "num-rational", - "num-traits 0.2.19", + "num-traits", "once_cell", "paste", "phf", @@ -597,9 +600,9 @@ dependencies = [ [[package]] name = "brotli" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -628,9 +631,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "serde", @@ -729,7 +732,7 @@ dependencies = [ "lazy_static", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", ] @@ -742,7 +745,7 @@ dependencies = [ "cairo-lang-utils 1.0.0-rc0", "indoc", "num-bigint", - "num-traits 0.2.19", + "num-traits", "serde", "thiserror", ] @@ -756,7 +759,7 @@ dependencies = [ "cairo-lang-utils 2.7.0", "indoc", "num-bigint", - "num-traits 0.2.19", + "num-traits", "parity-scale-codec", "serde", ] @@ -976,7 +979,7 @@ dependencies = [ "itertools 0.10.5", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", ] @@ -1000,7 +1003,7 @@ dependencies = [ "itertools 0.12.1", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "once_cell", "salsa", "smol_str", @@ -1021,7 +1024,7 @@ dependencies = [ "itertools 0.10.5", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", "unescaper", @@ -1041,7 +1044,7 @@ dependencies = [ "colored", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", "unescaper", @@ -1104,7 +1107,7 @@ checksum = "3d55dcf98a6e1a03e0b36129fad4253f9e6666a1746ab9c075d212ba68a4e9c1" dependencies = [ "cairo-lang-debug 2.7.0", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -1157,7 +1160,7 @@ dependencies = [ "keccak", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "rand", "sha2", "smol_str", @@ -1183,7 +1186,7 @@ dependencies = [ "itertools 0.10.5", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", ] @@ -1208,7 +1211,7 @@ dependencies = [ "indoc", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "once_cell", "salsa", "smol_str", @@ -1229,7 +1232,7 @@ dependencies = [ "lalrpop 0.19.12", "lalrpop-util 0.19.12", "num-bigint", - "num-traits 0.2.19", + "num-traits", "regex", "salsa", "serde", @@ -1254,7 +1257,7 @@ dependencies = [ "lalrpop-util 0.20.2", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "once_cell", "regex", "salsa", @@ -1291,7 +1294,7 @@ dependencies = [ "cairo-lang-utils 2.7.0", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "thiserror", ] @@ -1320,7 +1323,7 @@ dependencies = [ "cairo-lang-utils 2.7.0", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "thiserror", ] @@ -1367,7 +1370,7 @@ dependencies = [ "cairo-lang-syntax 2.7.0", "cairo-lang-utils 2.7.0", "itertools 0.12.1", - "num-traits 0.2.19", + "num-traits", "once_cell", "salsa", "serde", @@ -1394,7 +1397,7 @@ dependencies = [ "itertools 0.10.5", "log", "num-bigint", - "num-traits 0.2.19", + "num-traits", "thiserror", ] @@ -1414,7 +1417,7 @@ dependencies = [ "indoc", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "starknet-types-core", "thiserror", ] @@ -1461,7 +1464,7 @@ dependencies = [ "log", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "once_cell", "serde", "serde_json", @@ -1515,7 +1518,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "once_cell", "serde", "serde_json", @@ -1535,7 +1538,7 @@ dependencies = [ "cairo-lang-filesystem 1.0.0-rc0", "cairo-lang-utils 1.0.0-rc0", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", "thiserror", @@ -1552,7 +1555,7 @@ dependencies = [ "cairo-lang-filesystem 2.7.0", "cairo-lang-utils 2.7.0", "num-bigint", - "num-traits 0.2.19", + "num-traits", "salsa", "smol_str", "unescaper", @@ -1605,7 +1608,7 @@ dependencies = [ "log", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", "time", ] @@ -1617,10 +1620,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd5c8c127b9362a12ffb9dede38e792c81b4ded5a98b448baec157b745f47d1" dependencies = [ "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.7.0", "itertools 0.12.1", "num-bigint", - "num-traits 0.2.19", + "num-traits", "parity-scale-codec", "schemars", "serde", @@ -1644,7 +1647,7 @@ dependencies = [ "num-bigint", "num-integer", "num-prime", - "num-traits 0.2.19", + "num-traits", "rand", "rust_decimal", "serde", @@ -1691,9 +1694,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -1708,13 +1711,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", - "num-traits 0.2.19", + "num-traits", "serde", "windows-targets 0.52.6", ] @@ -1731,9 +1734,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" dependencies = [ "clap_builder", "clap_derive", @@ -1741,9 +1744,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" dependencies = [ "anstream", "anstyle", @@ -1753,21 +1756,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "coins-bip32" @@ -1823,31 +1826,31 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", + "once_cell", "unicode-width", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1858,9 +1861,9 @@ checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" [[package]] name = "const-hex" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -1877,18 +1880,18 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -1934,9 +1937,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1952,9 +1955,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1971,9 +1974,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -2057,7 +2060,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -2079,7 +2082,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -2129,7 +2132,27 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.90", + "syn 2.0.95", +] + +[[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.95", ] [[package]] @@ -2201,6 +2224,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "dunce" version = "1.0.5" @@ -2263,15 +2297,15 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -2326,12 +2360,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2480,7 +2514,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.90", + "syn 2.0.95", "toml 0.8.19", "walkdir", ] @@ -2498,7 +2532,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -2524,7 +2558,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.90", + "syn 2.0.95", "tempfile", "thiserror", "tiny-keccak", @@ -2601,7 +2635,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", "url", @@ -2659,7 +2693,7 @@ dependencies = [ "tokio", "tracing", "walkdir", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -2674,9 +2708,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" @@ -2722,6 +2756,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2764,9 +2804,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -2779,9 +2819,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -2789,15 +2829,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -2806,9 +2846,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-locks" @@ -2822,26 +2862,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -2855,9 +2895,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -2882,9 +2922,9 @@ dependencies = [ [[package]] name = "genco" -version = "0.17.9" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afac3cbb14db69ac9fef9cdb60d8a87e39a7a527f85a81a923436efa40ad42c6" +checksum = "a35958104272e516c2a5f66a9d82fba4784d2b585fc1e2358b8f96e15d342995" dependencies = [ "genco-macros", "relative-path", @@ -2893,13 +2933,13 @@ dependencies = [ [[package]] name = "genco-macros" -version = "0.17.9" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553630feadf7b76442b0849fd25fdf89b860d933623aec9693fed19af0400c78" +checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -2928,15 +2968,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" @@ -2947,8 +2987,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2965,12 +3005,12 @@ dependencies = [ [[package]] name = "good_lp" -version = "1.8.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3198bd13dea84c76a64621d6ee8ee26a4960a9a0d538eca95ca8f1320a469ac9" +checksum = "10efcd6c7d6f84cb5b4f9155248e0675deab9cfb92d0edbcb25cb81490b65ae7" dependencies = [ "fnv", - "minilp", + "microlp", ] [[package]] @@ -2996,7 +3036,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -3005,17 +3045,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.5.0", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -3048,6 +3088,17 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashers" version = "1.0.1" @@ -3116,11 +3167,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3136,9 +3187,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3163,7 +3214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -3174,22 +3225,22 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -3205,9 +3256,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -3229,15 +3280,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "httparse", "httpdate", @@ -3256,7 +3307,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.32", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -3264,18 +3315,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.4.1", + "http 1.2.0", + "hyper 1.5.2", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.20", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", ] @@ -3287,7 +3338,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.2", "hyper-util", "native-tls", "tokio", @@ -3297,29 +3348,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.2", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3338,6 +3388,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -3352,12 +3520,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3370,7 +3549,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -3405,13 +3584,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.95", ] [[package]] @@ -3439,12 +3618,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "serde", ] @@ -3480,10 +3659,11 @@ dependencies = [ "async-trait", "axum", "ethers", + "futures", "lazy_static", "listeners", "rand", - "reqwest 0.12.7", + "reqwest 0.12.12", "serde", "serde_json", "starknet-accounts", @@ -3495,21 +3675,22 @@ dependencies = [ "starknet-signers", "thiserror", "tokio", + "tokio-tungstenite 0.21.0", "universal-sierra-compiler", "url", ] [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "iri-string" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f755bd3806e06ad4f366f92639415d99a339a2c7ecf8c26ccea2097c11cb6" +checksum = "dc0f0a572e8ffe56e2ff4f769f32ffe919282c3916799f8b68688b6030063bea" dependencies = [ "memchr", "serde", @@ -3561,9 +3742,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -3576,10 +3757,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3599,9 +3781,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -3656,7 +3838,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -3679,14 +3861,14 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.7", + "regex-automata 0.4.9", ] [[package]] name = "lambdaworks-crypto" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", "serde", @@ -3696,9 +3878,9 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" dependencies = [ "serde", "serde_json", @@ -3721,9 +3903,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -3737,9 +3919,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "listeners" @@ -3753,6 +3935,12 @@ dependencies = [ "windows", ] +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -3771,11 +3959,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -3795,10 +3983,11 @@ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "matrixmultiply" -version = "0.2.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ + "autocfg", "rawpointer", ] @@ -3818,6 +4007,16 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "microlp" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8113ec0619201ef0ead05ecafe9ba59b525ab73508456b8d35dbaf810cd07704" +dependencies = [ + "log", + "sprs", +] + [[package]] name = "mime" version = "0.3.17" @@ -3834,16 +4033,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minilp" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a7750a9e5076c660b7bec5e6457b4dbff402b9863c8d112891434e18fd5385" -dependencies = [ - "log", - "sprs", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3861,20 +4050,19 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -3899,14 +4087,16 @@ dependencies = [ [[package]] name = "ndarray" -version = "0.13.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" dependencies = [ "matrixmultiply", "num-complex", "num-integer", - "num-traits 0.2.19", + "num-traits", + "portable-atomic", + "portable-atomic-util", "rawpointer", ] @@ -3949,19 +4139,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", - "num-traits 0.2.19", + "num-traits", "rand", "serde", ] [[package]] name = "num-complex" -version = "0.2.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ - "autocfg", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3976,7 +4165,7 @@ version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3987,7 +4176,7 @@ checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" dependencies = [ "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -4002,7 +4191,7 @@ dependencies = [ "num-bigint", "num-integer", "num-modular", - "num-traits 0.2.19", + "num-traits", "rand", ] @@ -4014,19 +4203,10 @@ checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", ] -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.19", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -4034,7 +4214,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -4065,7 +4244,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -4079,18 +4258,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -4125,9 +4304,9 @@ dependencies = [ [[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", @@ -4146,7 +4325,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -4157,18 +4336,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.2+3.3.2" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] [[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", @@ -4258,7 +4437,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.4", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -4342,7 +4521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap 2.7.0", ] [[package]] @@ -4357,35 +4536,35 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared 0.11.3", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared 0.11.3", "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared 0.11.2", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -4394,16 +4573,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] @@ -4414,29 +4593,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -4456,9 +4635,24 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "portable-atomic" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "powerfmt" @@ -4483,22 +4677,22 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", - "yansi", + "yansi 1.0.1", ] [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -4535,25 +4729,25 @@ dependencies = [ [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bitflags 2.6.0", "lazy_static", - "num-traits 0.2.19", + "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "unarray", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -4649,9 +4843,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.4" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -4669,14 +4863,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -4690,13 +4884,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -4707,9 +4901,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "regex_generate" @@ -4742,7 +4936,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.32", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -4771,9 +4965,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", @@ -4781,12 +4975,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", - "hyper-rustls 0.27.3", + "hyper 1.5.2", + "hyper-rustls 0.27.5", "hyper-tls", "hyper-util", "ipnet", @@ -4797,14 +4991,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", "tokio-native-tls", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", @@ -4917,7 +5112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -4949,15 +5144,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4974,9 +5169,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "rustls-pki-types", @@ -4996,19 +5191,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" [[package]] name = "rustls-webpki" @@ -5033,9 +5227,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -5092,26 +5286,26 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", ] [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.95", ] [[package]] @@ -5125,9 +5319,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -5154,7 +5348,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -5220,9 +5414,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -5230,9 +5424,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] @@ -5251,22 +5445,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -5277,16 +5471,16 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -5316,9 +5510,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5337,15 +5531,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -5355,14 +5549,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -5371,7 +5565,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -5400,7 +5594,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -5476,7 +5670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", - "num-traits 0.2.19", + "num-traits", "thiserror", "time", ] @@ -5487,6 +5681,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -5513,9 +5713,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5559,15 +5759,22 @@ dependencies = [ [[package]] name = "sprs" -version = "0.7.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec63571489873d4506683915840eeb1bb16b3198ee4894cc6f2fe3013d505e56" +checksum = "704ef26d974e8a452313ed629828cd9d4e4fa34667ca1ad9d6b1fffa43c6e166" dependencies = [ "ndarray", "num-complex", - "num-traits 0.1.43", + "num-traits", + "smallvec", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "starknet-accounts" version = "0.11.0" @@ -5577,7 +5784,7 @@ dependencies = [ "async-trait", "auto_impl", "starknet-core", - "starknet-crypto 0.7.2", + "starknet-crypto 0.7.3", "starknet-providers", "starknet-signers", "thiserror", @@ -5613,7 +5820,7 @@ dependencies = [ "serde_json_pythonic", "serde_with", "sha3", - "starknet-crypto 0.7.2", + "starknet-crypto 0.7.3", "starknet-types-core", ] @@ -5628,7 +5835,7 @@ dependencies = [ "hmac", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "rfc6979", "sha2", "starknet-crypto-codegen", @@ -5648,7 +5855,7 @@ dependencies = [ "hmac", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "rfc6979", "sha2", "starknet-crypto-codegen", @@ -5659,16 +5866,16 @@ dependencies = [ [[package]] name = "starknet-crypto" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a5064173a8e8d2675e67744fd07f310de44573924b6b7af225a6bdd8102913" +checksum = "ded22ccf4cb9e572ce3f77de6066af53560cd2520d508876c83bb1e6b29d5cbc" dependencies = [ "crypto-bigint", "hex", "hmac", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "rfc6979", "sha2", "starknet-curve 0.5.1", @@ -5684,7 +5891,7 @@ checksum = "bbc159a1934c7be9761c237333a57febe060ace2bc9e3b337a59a37af206d19f" dependencies = [ "starknet-curve 0.4.2", "starknet-ff", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -5721,6 +5928,7 @@ dependencies = [ "anyhow", "clap", "futures", + "reqwest 0.12.12", "serde", "serde_json", "serial_test", @@ -5741,16 +5949,17 @@ version = "0.2.3" dependencies = [ "blockifier", "cairo-lang-starknet-classes", + "cairo-vm", "clap", "ethers", "hex", - "indexmap 2.5.0", + "indexmap 2.7.0", "nonzero_ext", "openssl", "parking_lot 0.12.3", "rand", "rand_mt", - "reqwest 0.12.7", + "reqwest 0.12.12", "serde", "serde_json", "starknet-core", @@ -5760,7 +5969,6 @@ dependencies = [ "starknet_api", "thiserror", "tracing", - "universal-sierra-compiler", "url", ] @@ -5778,7 +5986,7 @@ dependencies = [ "rand", "rand_chacha", "regex_generate", - "reqwest 0.12.7", + "reqwest 0.12.12", "serde", "serde_json", "serde_yaml", @@ -5819,7 +6027,7 @@ dependencies = [ "serde", "serde_json", "starknet-core", - "starknet-crypto 0.7.2", + "starknet-crypto 0.7.3", "starknet-types-core", "starknet_api", "thiserror", @@ -5872,22 +6080,22 @@ dependencies = [ "getrandom", "rand", "starknet-core", - "starknet-crypto 0.7.2", + "starknet-crypto 0.7.3", "thiserror", ] [[package]] name = "starknet-types-core" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6bacf0ba19bc721e518bc4bf389ff13daa8a7c5db5fd320600473b8aa9fcbd" +checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" dependencies = [ "lambdaworks-crypto", "lambdaworks-math", "lazy_static", "num-bigint", "num-integer", - "num-traits 0.2.19", + "num-traits", "serde", ] @@ -5899,9 +6107,9 @@ checksum = "1b505c9c076d9fce854304bd743c93ea540ebea6b16ec96819b07343a3aa2c7c" dependencies = [ "bitvec", "cairo-lang-starknet-classes", - "derive_more", + "derive_more 0.99.18", "hex", - "indexmap 2.5.0", + "indexmap 2.7.0", "itertools 0.12.1", "once_cell", "primitive-types", @@ -5990,7 +6198,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -6003,7 +6211,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -6045,9 +6253,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -6062,13 +6270,24 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -6119,12 +6338,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -6152,22 +6372,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -6202,9 +6422,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -6225,9 +6445,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -6242,11 +6462,21 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -6259,9 +6489,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -6283,7 +6513,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -6308,12 +6538,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.13", - "rustls-pki-types", + "rustls 0.23.20", "tokio", ] @@ -6328,15 +6557,39 @@ dependencies = [ "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", - "tungstenite", + "tungstenite 0.20.1", "webpki-roots", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.21.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.24.0", +] + [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -6377,11 +6630,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -6398,6 +6651,21 @@ dependencies = [ "futures-util", "pin-project", "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -6416,7 +6684,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -6428,11 +6696,11 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -6449,9 +6717,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -6461,20 +6729,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -6503,9 +6771,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -6545,6 +6813,43 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -6580,51 +6885,33 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-sierra-compiler" @@ -6682,9 +6969,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -6697,6 +6984,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -6715,9 +7014,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" dependencies = [ "getrandom", ] @@ -6767,9 +7066,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -6778,36 +7077,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6815,28 +7114,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -6919,7 +7218,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -6930,7 +7229,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", ] [[package]] @@ -7113,9 +7412,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] @@ -7130,6 +7429,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -7160,18 +7471,18 @@ dependencies = [ [[package]] name = "xshell" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" +checksum = "9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373d" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" +checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547" [[package]] name = "yansi" @@ -7179,6 +7490,36 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -7197,7 +7538,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", + "synstructure", ] [[package]] @@ -7217,7 +7579,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.95", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.95", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ec5fa6fff..2d88a0cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ keywords = ["starknet", "cairo", "testnet", "local", "server"] [workspace.dependencies] # axum -axum = { version = "0.7" } +axum = { version = "0.7", features = ["ws"] } http-body-util = { version = "0.1" } tower-http = { version = "0.5", features = ["full"] } @@ -107,6 +107,7 @@ unsafe-libyaml = "0.2.10" h2 = "0.4" ethers = { version = "2.0.11" } +cargo-platform = "=0.1.8" # 0.1.9 is incompatible with rustc 1.76.0 openssl = { version = "0.10", features = ["vendored"] } @@ -116,6 +117,7 @@ parking_lot = "0.12.3" serial_test = "3.1.1" hex = "0.4.3" lazy_static = { version = "1.4.0" } +tokio-tungstenite = { version = "0.21.0" } listeners = "0.2.1" # https://github.com/paritytech/parity-scale-codec/issues/656 diff --git a/contracts/test_artifacts/cairo1/panicking_contract/compilation_info.txt b/contracts/test_artifacts/cairo1/panicking_contract/compilation_info.txt index 4bcb5814c..79a0ec6f2 100644 --- a/contracts/test_artifacts/cairo1/panicking_contract/compilation_info.txt +++ b/contracts/test_artifacts/cairo1/panicking_contract/compilation_info.txt @@ -1 +1 @@ -Compiled using cairo 2.2.0 \ No newline at end of file +Compiled using cairo 2.8.0 \ No newline at end of file diff --git a/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.cairo b/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.cairo index 3fdf09149..4aa8893b4 100644 --- a/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.cairo +++ b/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.cairo @@ -1,17 +1,30 @@ +use core::starknet::ContractAddress; + #[starknet::interface] trait IPanickingContract { fn create_panic(self: @TContractState, panic_reason: felt252); + fn create_panic_in_another_contract(self: @TContractState, address: ContractAddress, panic_reason: felt252); } #[starknet::contract] mod PanickingContract { + use core::starknet::{ContractAddress, call_contract_syscall}; + #[storage] struct Storage {} - #[external(v0)] + #[abi(embed_v0)] impl PanickingContract of super::IPanickingContract { fn create_panic(self: @ContractState, panic_reason: felt252) { panic_with_felt252(panic_reason); } + + fn create_panic_in_another_contract(self: @ContractState, address: ContractAddress, panic_reason: felt252) { + call_contract_syscall( + address, + selector!("create_panic"), + array![panic_reason].span() + ).unwrap(); + } } } diff --git a/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.sierra b/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.sierra index e50eaa378..54688dafd 100644 --- a/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.sierra +++ b/contracts/test_artifacts/cairo1/panicking_contract/panicking_contract.sierra @@ -1 +1 @@ -{"sierra_program":["0x1","0x3","0x0","0x2","0x2","0x0","0x98","0x68","0x14","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x426f78","0x800000000000000700000000000000000000000000000001","0x1","0x11","0x537472756374","0x800000000000000f00000000000000000000000000000001","0x0","0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3","0x456e756d","0x800000000000000700000000000000000000000000000003","0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7","0x2","0x4172726179","0x800000000000000300000000000000000000000000000001","0x536e617073686f74","0x4","0x800000000000000700000000000000000000000000000002","0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62","0x5","0x6","0x800000000000000f00000000000000000000000000000002","0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672","0x800000000000000300000000000000000000000000000003","0x9","0xcc5e86243f861d2d64b08c35db21013e773ac5cf10097946fe0011304886d5","0x8","0xa","0x12182c50391781ceaa4183d4751183afb1e374a1edf35d25bdb7132e74891de","0x4275696c74696e436f737473","0x800000000000000700000000000000000000000000000000","0x53797374656d","0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6","0x7","0x753332","0x66656c74323532","0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511","0x4761734275696c74696e","0x3c","0x7265766f6b655f61705f747261636b696e67","0x77697468647261775f676173","0x6272616e63685f616c69676e","0x73746f72655f74656d70","0x66756e6374696f6e5f63616c6c","0x3","0x656e756d5f6d61746368","0x12","0x7374727563745f6465636f6e737472756374","0x61727261795f6c656e","0x736e617073686f745f74616b65","0x10","0x64726f70","0x7533325f636f6e7374","0x72656e616d65","0x7533325f6571","0x61727261795f6e6577","0x66656c743235325f636f6e7374","0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473","0x61727261795f617070656e64","0x7374727563745f636f6e737472756374","0x656e756d5f696e6974","0xf","0x13","0xe","0x6765745f6275696c74696e5f636f737473","0xd","0x77697468647261775f6761735f616c6c","0xc","0xb","0x4f7574206f6620676173","0x4661696c656420746f20646573657269616c697a6520706172616d202331","0x61727261795f736e617073686f745f706f705f66726f6e74","0x6a756d70","0x756e626f78","0x92","0xffffffffffffffff","0x5f","0x50","0x15","0x21","0x16","0x17","0x18","0x19","0x1a","0x1b","0x1c","0x1d","0x1e","0x1f","0x20","0x22","0x42","0x23","0x24","0x25","0x26","0x27","0x29","0x2a","0x28","0x2b","0x3b","0x2c","0x2d","0x2e","0x2f","0x30","0x31","0x32","0x33","0x34","0x35","0x36","0x37","0x38","0x39","0x3a","0x3d","0x3e","0x3f","0x40","0x41","0x43","0x44","0x45","0x46","0x47","0x48","0x49","0x4a","0x4b","0x4c","0x4d","0x4e","0x4f","0x51","0x52","0x53","0x54","0x55","0x56","0x57","0x58","0x59","0x74","0x79","0x83","0x6d","0x8a","0x5d3","0x14091307120504110605100f0e0505050d090c0b0a09080706050403020100","0x51b09190b12051a050a091907180908070e050a09170716050a0913071505","0xe05060526090c0b202520241d0523052209190b0221201f1e0908071d051c","0x532160505313005052f0905052c052e052d1605052c092b092a0929280227","0x505390605053509383405052c34050537090e053634050535340505330605","0x4005052c050e3f050e3e1d05053d1a05053d0605053c0605052c3b0e053a06","0x5052c45050535450505334505053d09444305052c09423f05052c4105052c","0xe3e2305053d1605053d12050535120505331c0505354605052f0e2e052d45","0x2c090e2e050e3e06050549480e053a160505350e050535470e053a090e3f05","0x60505370605054b2e05052f050e2e050e3e0e05053d094a2e05052c150505","0x50909094c4605052c050e46050e3e050e30050e3e3005052c090e30050e3e","0x91d054d052e052e09094d05090e0923160e4e15120e4d0e05090e0509094d","0x5090e0945054f46054d0e1a05160912054d05120515091a1c0e4d051d0512","0x3f051d09343f0e4d0541051a0941054d0543051c0943054d051c052309094d","0x534054509094d0530051d0940300e4d0506051a0906054d05094609094d05","0xe090951094d0e50000e410900054d050005430950054d054005450900054d","0x4d055305300953054d0509060952054d05093409094d0546053f09094d0509","0x5605520956054d0554550e500955054d0509000954054d0553520e40095305","0x555095a054d050e05540959054d051505530958054d051205150957054d05","0x54d055c0557095c054d05095609094d05090e095b5a595812055b054d0557","0x5a0961054d05095909094d05090e09605f0e5e5d510e4d0e5c15122e58095c","0x965054d054605300964054d0563055c09094d0562055b0963620e4d056105","0x5090e0969056867054d0e66055d0951054d055105150966054d0565640e51","0x4d056b0561096c6b0e4d056a0560096a054d05093409094d0567055f09094d","0x4d05510515096f054d056e0566096e054d056d0563096d054d056c05620909","0x727170120573054d056f05550972054d050e05540971054d055d0553097005","0x76054d055d05530975054d055105150974054d0569055209094d05090e0973","0x53f09094d05090e0968777675120568054d057405550977054d050e055409","0x78280e400978054d057805300978054d0509640928054d05093409094d0546","0x515097c054d057b0552097b054d05797a0e50097a054d0509000979054d05","0x12057f054d057c0555097e054d050e0554097d054d05600553095e054d055f","0x80054d05093409094d051c056709094d0545056509094d05090e097f7e7d5e","0x83054d0509000982054d0581800e400981054d058105300981054d05096909","0x4d051505530986054d051205150985054d058405520984054d0582830e5009","0x9094d05090e0989888786120589054d058505550988054d050e0554098705","0xe40098a054d058a0530098a054d050964094f054d05093409094d052e0567","0x98e054d058d0552098d054d058b8c0e50098c054d050900098b054d058a4f","0x92054d058e05550991054d050e05540990054d05230553098f054d05160515","0x4d05090e091205932e0e0e4d0e05056a0905054d05090523099291908f1205","0x99405096e0923054d0515056d0916054d050e056c0915054d052e056b0909","0x6d0916054d0512056c091a054d051c0570091c054d05096f09094d05090e09","0x9546054d0e230571091d054d051d052e091d054d051605620923054d051a05","0x54d054105740941054d054305730943054d0546057209094d05090e094505","0x45056509094d05090e0906340e0506054d053f05750934054d051d052e093f","0x54005750900054d051d052e0940054d053005760930054d05096f09094d05","0x92e054d05050e0e40090e054d05093409094d0509055b0950000e0550054d","0x23054d051605680916054d051505770915054d052e120e500912054d050900","0x460506450e960930160e1605092e0e05093f4140091216414009122e230505","0x970509"],"sierra_program_debug_info":{"type_names":[],"libfunc_names":[],"user_func_names":[]},"contract_class_version":"0.1.0","entry_points_by_type":{"EXTERNAL":[{"selector":"0x2f30ca48a88216d700251f5f06cb6dbdc3420bdef67879ba6836b55cf0d0dfd","function_idx":0}],"L1_HANDLER":[],"CONSTRUCTOR":[]},"abi":[{"type":"impl","name":"PanickingContract","interface_name":"panicking_contract::panicking_contract::IPanickingContract"},{"type":"interface","name":"panicking_contract::panicking_contract::IPanickingContract","items":[{"type":"function","name":"create_panic","inputs":[{"name":"panic_reason","type":"core::felt252"}],"outputs":[],"state_mutability":"view"}]},{"type":"event","name":"panicking_contract::panicking_contract::PanickingContract::Event","kind":"enum","variants":[]}]} \ No newline at end of file +{"sierra_program":["0x1","0x6","0x0","0x2","0x8","0x0","0xa9","0x57","0x16","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x436f6e7374","0x800000000000000000000000000000000000000000000002","0x1","0x12","0x2","0x4661696c656420746f20646573657269616c697a6520706172616d202332","0x526573756c743a3a756e77726170206661696c65642e","0x4172726179","0x800000000000000300000000000000000000000000000001","0x536e617073686f74","0x800000000000000700000000000000000000000000000001","0x3","0x537472756374","0x800000000000000700000000000000000000000000000002","0x0","0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62","0x4","0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3","0x5","0x2f30ca48a88216d700251f5f06cb6dbdc3420bdef67879ba6836b55cf0d0dfd","0x436f6e747261637441646472657373","0x800000000000000700000000000000000000000000000000","0x4661696c656420746f20646573657269616c697a6520706172616d202331","0x4f7574206f6620676173","0x4275696c74696e436f737473","0x53797374656d","0x800000000000000f00000000000000000000000000000001","0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672","0x800000000000000300000000000000000000000000000003","0xd","0x456e756d","0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6","0x6","0xe","0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473","0x66656c74323532","0x800000000000000700000000000000000000000000000003","0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511","0x11","0x426f78","0x4761734275696c74696e","0x31","0x7265766f6b655f61705f747261636b696e67","0x77697468647261775f676173","0x6272616e63685f616c69676e","0x7374727563745f6465636f6e737472756374","0x656e61626c655f61705f747261636b696e67","0x73746f72655f74656d70","0x61727261795f736e617073686f745f706f705f66726f6e74","0x756e626f78","0x72656e616d65","0x656e756d5f696e6974","0x13","0x6a756d70","0x7374727563745f636f6e737472756374","0x656e756d5f6d61746368","0x64697361626c655f61705f747261636b696e67","0x64726f70","0x14","0x61727261795f6e6577","0x636f6e73745f61735f696d6d656469617465","0x10","0x61727261795f617070656e64","0xf","0x15","0xc","0x6765745f6275696c74696e5f636f737473","0xb","0x77697468647261775f6761735f616c6c","0xa","0x9","0x21adb5788e32c84f69a1863d85ef9394b7bf761a0ce1190f826984e5075c371","0x8","0x7","0x736e617073686f745f74616b65","0x63616c6c5f636f6e74726163745f73797363616c6c","0x10e","0xffffffffffffffff","0x56","0x46","0x27","0x17","0x18","0x19","0x1a","0x1b","0x1c","0x1d","0x1e","0x1f","0x20","0x38","0x21","0x22","0x23","0x24","0x25","0x26","0x28","0x29","0x2a","0x2b","0x2c","0x2d","0x2e","0x2f","0x30","0x32","0x33","0x34","0x35","0x36","0x37","0x39","0x100","0x72","0x77","0xef","0xeb","0x84","0x89","0xda","0x9e","0xca","0x3a","0xbc","0x3b","0x3c","0x3d","0x3e","0x3f","0x40","0x41","0x42","0x43","0x44","0x45","0x47","0x48","0x49","0x4a","0x4b","0x4c","0x4d","0x4e","0x4f","0x50","0x51","0x52","0x53","0x54","0x55","0xf3","0x57","0x58","0x59","0x5a","0x5b","0x5c","0x5d","0x5e","0x5f","0x60","0x61","0x62","0x64","0x960","0x100f13051211100f0e050d0c06050b0a090706050403080706050403020100","0x111d0f021c181b1a0706050403190706050403181716070605040315051411","0x2721182614111d0f2507060504032405230522111f210e05200514111f0f1e","0x53311050532113115050530112f112e112d2c022b06050d2a290506052811","0x36290505391138370505321305053211073705073606050535060505340605","0x4005053f0605053e0605053c3d05053c1305053c113b3705053a0507370507","0x32440505324305053205074205073624050539200505390605054106050532","0x53c114a1505053c4905053f2905053c4805053f1147460505321145420505","0x5073623050539114e15050532150505390e05053c0e05054d4c05053f4b05","0x230751151307500705110705111150051111114f0505053f0705053f110742","0x231113055005130515111150051113114b0550050e050e111150051107114c","0x5500544054b114405500548054c1111500511071146052448490750074b05","0x11370511441140055005240546114205500549054811240550052005491120","0x46114205500546054811060550052905241129055005112011115005110711","0x43075007420523111150051107113d05523705500740054211400550050605","0x11115005000506111150054305291111500511401111500511071154055300","0x7541156055005560500115605500511431155055005113d11115005370537","0x115a0550055905571159055005575807561158055005115511570550055655","0x5a0550055a055a110705500507055911150550051505581113055005130515","0x55005115b11115005540529111150051140111150051107115a0715131305","0x11115005110711605f075e5d5c0750075b15130e5d115b0550055b055c115b","0x6405500562630756116305500511551162055005376107541161055005113d","0x55005070559115d0550055d0558115c0550055c0515115305500564055711","0x113d111150053705371111500511071153075d5c13055305500553055a1107","0x115511670550056665075411660550056605001166055005115f1165055005","0x58115f0550055f0515116a0550056905571169055005676807561168055005","0x1107116a07605f13056a0550056a055a110705500507055911600550056005","0x116b055005113d11115005420529111150053d056011115005114011115005","0x112c0550051155116d0550056c6b0754116c0550056c0500116c0550051161","0x550051505581113055005130515116f0550056e0557116e0550056d2c0756","0x62111150051107116f07151313056f0550056f055a11070550050705591115","0x70075411710550057105001171055005115f1170055005113d111150050e05","0x1511740550055e0557115e0550057273075611730550051155117205500571","0x57405500574055a1107055005070559114c0550054c055811230550052305","0x1150051107114c2307751513075007051107051111500511111174074c2313","0x48490750074b05231113055005130515111150051113114b0550050e050e11","0x5005200549112005500544054b114405500548054c11115005110711460576","0x11115005110711117705114411400550052405461142055005490548112405","0x11400550050605461142055005460548110605500529052411290550051120","0x711540579004307500737130763111150051107113d057837055007400542","0x1111500511071157057a565507500742052311430550054305151111500511","0x5b055005550548115a055005590549115905500558054b115805500556054c","0x5d0524115d055005112011115005110711117b051144115c0550055a054611","0x61057c600550075c0542115c0550055f0546115b055005570548115f055005","0x1111500511401111500511071164057d63620750075b052311115005110711","0x5113d11115005000564111150056005371111500563050611115005620529","0x5115511660550056553075411650550056505001165055005114311530550","0x5581143055005430515116905500568055711680550056667075611670550","0x51107116907154313056905500569055a1107055005070559111505500515","0x76a15430e5d116a0550056a055c116a055005115b11115005640529111150","0x6e0754116e055005113d111150051140111150051107112c6d077e6c6b0750","0x567111150057105661172710750056f056511700550051153116f05500560","0x69116b0550056b051511730550057305681170055005700500117305500572","0x111150057f0562111150051107118382810e807f745e0e5007737000076c15","0x1187055005860567111150058505661186850750058405651184055005113d","0x5e0550055e0558116b0550056b0515118905500588056b118805500587056a","0x5661111500511071189745e6b13058905500589055a117405500574055911","0x8b8a0754118b0550058b0500118b055005116c118a055005113d1111500583","0x515118e0550058d0557118d055005528c0756118c05500511551152055005","0x13058e0550058e055a11820550058205591181055005810558116b0550056b","0x1111500500056411115005600537111150051140111150051107118e82816b","0x1191055005908f075411900550059005001190055005115f118f055005113d","0x6d0550056d0515119405500593055711930550059192075611920550051155","0x1194072c6d13059405500594055a1107055005070559112c0550052c055811","0x5005000564111150055b052911115005610560111150051140111150051107","0x550059695075411960550059605001196055005116d1195055005113d1111","0x5005430515119a055005990557119905500597980756119805500511551197","0x7154313059a0550059a055a11070550050705591115055005150558114305","0x110711119b051144115105500554051511115005420529111150051107119a","0x1150051140115105500513051511115005420529111150053d056011115005","0x9e0550059d9c0754119d0550059d0500119d0550051161119c055005113d11","0x5500551051511a1055005a0055711a00550059e9f0756119f055005115511","0xa10715511305a1055005a1055a110705500507055911150550051505581151","0xa3050011a3055005115f11a2055005113d111150050e056211115005110711","0x5711a6055005a4a5075611a5055005115511a4055005a3a2075411a3055005","0x1107055005070559114c0550054c0558112305500523051511a7055005a605","0x1113110e0705114244431113154443111307a7074c231305a7055005a7055a","0xa80e0705114244431113154443"],"sierra_program_debug_info":{"type_names":[],"libfunc_names":[],"user_func_names":[]},"contract_class_version":"0.1.0","entry_points_by_type":{"EXTERNAL":[{"selector":"0x1017d0207c04f7ba93b6432c22f38693c884e40738af93551399097b24d0f27","function_idx":1},{"selector":"0x2f30ca48a88216d700251f5f06cb6dbdc3420bdef67879ba6836b55cf0d0dfd","function_idx":0}],"L1_HANDLER":[],"CONSTRUCTOR":[]},"abi":[{"type":"impl","name":"PanickingContract","interface_name":"panicking_contract::panicking_contract::IPanickingContract"},{"type":"interface","name":"panicking_contract::panicking_contract::IPanickingContract","items":[{"type":"function","name":"create_panic","inputs":[{"name":"panic_reason","type":"core::felt252"}],"outputs":[],"state_mutability":"view"},{"type":"function","name":"create_panic_in_another_contract","inputs":[{"name":"address","type":"core::starknet::contract_address::ContractAddress"},{"name":"panic_reason","type":"core::felt252"}],"outputs":[],"state_mutability":"view"}]},{"type":"event","name":"panicking_contract::panicking_contract::PanickingContract::Event","kind":"enum","variants":[]}]} \ No newline at end of file diff --git a/crates/starknet-devnet-core/Cargo.toml b/crates/starknet-devnet-core/Cargo.toml index 1464e4449..d5128ed62 100644 --- a/crates/starknet-devnet-core/Cargo.toml +++ b/crates/starknet-devnet-core/Cargo.toml @@ -11,6 +11,7 @@ description = "Starknet core logic for devnet" [dependencies] blockifier = { workspace = true, features = ["testing"] } cairo-lang-starknet-classes = { workspace = true } +cairo-vm = { workspace = true } clap = { workspace = true } ethers = { workspace = true } starknet_api = { workspace = true, features = ["testing"] } @@ -28,7 +29,6 @@ tracing = { workspace = true } indexmap = { workspace = true } url = { workspace = true } nonzero_ext = { workspace = true } -usc = { workspace = true } parking_lot = { workspace = true } # necessary for installing reqwest in Docker diff --git a/crates/starknet-devnet-core/src/blocks/mod.rs b/crates/starknet-devnet-core/src/blocks/mod.rs index c38b7d857..dc9c39673 100644 --- a/crates/starknet-devnet-core/src/blocks/mod.rs +++ b/crates/starknet-devnet-core/src/blocks/mod.rs @@ -267,6 +267,14 @@ impl StarknetBlock { } } + pub fn create_empty_accepted() -> Self { + Self { + header: BlockHeader::default(), + transaction_hashes: vec![], + status: BlockStatus::AcceptedOnL2, + } + } + pub(crate) fn set_block_number(&mut self, block_number: u64) { self.header.block_number = BlockNumber(block_number) } diff --git a/crates/starknet-devnet-core/src/error.rs b/crates/starknet-devnet-core/src/error.rs index 4f9db613b..57d012471 100644 --- a/crates/starknet-devnet-core/src/error.rs +++ b/crates/starknet-devnet-core/src/error.rs @@ -8,6 +8,8 @@ use starknet_types::contract_address::ContractAddress; use starknet_types::contract_storage_key::ContractStorageKey; use thiserror::Error; +use crate::stack_trace::{gen_tx_execution_error_trace, ErrorStack}; + #[derive(Error, Debug)] pub enum Error { #[error(transparent)] @@ -16,12 +18,10 @@ pub enum Error { StateError(#[from] StateError), #[error(transparent)] BlockifierStateError(#[from] blockifier::state::errors::StateError), - #[error(transparent)] - BlockifierTransactionError(TransactionExecutionError), - #[error(transparent)] - BlockifierExecutionError(#[from] blockifier::execution::errors::EntryPointExecutionError), - #[error("{execution_error}")] - ExecutionError { execution_error: String, index: usize }, + #[error("{0:?}")] + ContractExecutionError(ErrorStack), + #[error("Execution error in simulating transaction no. {failure_index}: {error_stack:?}")] + ContractExecutionErrorInSimulation { failure_index: usize, error_stack: ErrorStack }, #[error("Types error: {0}")] TypesError(#[from] starknet_types::error::Error), #[error("I/O error: {0}")] @@ -96,8 +96,8 @@ pub enum StateError { #[derive(Debug, Error)] pub enum TransactionValidationError { - #[error("Provided max fee is not enough to cover the transaction cost.")] - InsufficientMaxFee, + #[error("The transaction's resources don't cover validation or the minimal transaction fee.")] + InsufficientResourcesForValidate, #[error("Account transaction nonce is invalid.")] InvalidTransactionNonce, #[error("Account balance is not enough to cover the transaction cost.")] @@ -123,7 +123,7 @@ impl From for Error { err @ TransactionExecutionError::DeclareTransactionError { .. } => { Error::ClassAlreadyDeclared { msg: err.to_string() } } - other => Self::BlockifierTransactionError(other), + other => Self::ContractExecutionError(gen_tx_execution_error_trace(&other)), } } } @@ -132,7 +132,7 @@ impl From for Error { fn from(value: FeeCheckError) -> Self { match value { FeeCheckError::MaxL1GasAmountExceeded { .. } | FeeCheckError::MaxFeeExceeded { .. } => { - TransactionValidationError::InsufficientMaxFee.into() + TransactionValidationError::InsufficientResourcesForValidate.into() } FeeCheckError::InsufficientFeeTokenBalance { .. } => { TransactionValidationError::InsufficientAccountBalance.into() @@ -148,7 +148,7 @@ impl From for Error { | TransactionFeeError::MaxFeeTooLow { .. } | TransactionFeeError::MaxL1GasPriceTooLow { .. } | TransactionFeeError::MaxL1GasAmountTooLow { .. } => { - TransactionValidationError::InsufficientMaxFee.into() + TransactionValidationError::InsufficientResourcesForValidate.into() } TransactionFeeError::MaxFeeExceedsBalance { .. } | TransactionFeeError::L1GasBoundsExceedBalance { .. } => { diff --git a/crates/starknet-devnet-core/src/lib.rs b/crates/starknet-devnet-core/src/lib.rs index 073af6d07..eb71f8402 100644 --- a/crates/starknet-devnet-core/src/lib.rs +++ b/crates/starknet-devnet-core/src/lib.rs @@ -6,6 +6,7 @@ pub mod error; pub mod messaging; mod predeployed_accounts; pub mod raw_execution; +pub mod stack_trace; pub mod starknet; mod state; mod system_contract; @@ -18,3 +19,4 @@ mod utils; pub mod utils; pub use blocks::StarknetBlock; +pub use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; diff --git a/crates/starknet-devnet-core/src/messaging/ethereum.rs b/crates/starknet-devnet-core/src/messaging/ethereum.rs index 696017bcf..0b2aa50b8 100644 --- a/crates/starknet-devnet-core/src/messaging/ethereum.rs +++ b/crates/starknet-devnet-core/src/messaging/ethereum.rs @@ -7,7 +7,7 @@ use ethers::prelude::*; use ethers::providers::{Http, Provider, ProviderError}; use ethers::types::{Address, BlockNumber, Log}; use k256::ecdsa::SigningKey; -use starknet_rs_core::types::Felt; +use starknet_rs_core::types::{Felt, Hash256}; use starknet_types::felt::felt_from_prefixed_hex; use starknet_types::rpc::contract_address::ContractAddress; use starknet_types::rpc::messaging::{MessageToL1, MessageToL2}; @@ -302,6 +302,7 @@ impl EthereumMessaging { /// /// * `log` - The log to be converted. pub fn message_to_l2_from_log(log: Log) -> DevnetResult { + let l1_transaction_hash = log.transaction_hash.map(|h| Hash256::from_bytes(h.to_fixed_bytes())); let parsed_log = ::decode_log(&log.into()).map_err(|e| { Error::MessagingError(MessagingError::EthersError(format!("Log parsing failed {}", e))) })?; @@ -318,6 +319,7 @@ pub fn message_to_l2_from_log(log: Log) -> DevnetResult { } Ok(MessageToL2 { + l1_transaction_hash, l2_contract_address: contract_address, entry_point_selector, l1_contract_address: ContractAddress::new(from_address)?, @@ -405,6 +407,7 @@ mod tests { }; let expected_message = MessageToL2 { + l1_transaction_hash: None, l1_contract_address: ContractAddress::new( felt_from_prefixed_hex(from_address).unwrap(), ) diff --git a/crates/starknet-devnet-core/src/messaging/mod.rs b/crates/starknet-devnet-core/src/messaging/mod.rs index 6937dc45e..db5943677 100644 --- a/crates/starknet-devnet-core/src/messaging/mod.rs +++ b/crates/starknet-devnet-core/src/messaging/mod.rs @@ -32,7 +32,8 @@ //! contract (`mockSendMessageFromL2` entrypoint). use std::collections::HashMap; -use starknet_rs_core::types::{BlockId, ExecutionResult, Hash256}; +use ethers::types::H256; +use starknet_rs_core::types::{BlockId, ExecutionResult, Felt, Hash256}; use starknet_types::rpc::messaging::{MessageToL1, MessageToL2}; use crate::error::{DevnetResult, Error, MessagingError}; @@ -54,13 +55,11 @@ pub struct MessagingBroker { /// For each time a message is supposed to be sent to L1, it is stored in this /// queue. The user may consume those messages using `consume_message_from_l2` /// to actually test `MessageToL1` emitted without running L1 node. - /// - /// Note: - /// `Hash256` is not directly supported as a HashMap key due to missing trait. - /// Using `String` instead. - pub l2_to_l1_messages_hashes: HashMap, + pub l2_to_l1_messages_hashes: HashMap, /// This list of messages that will be sent to L1 node at the next `postman/flush`. pub l2_to_l1_messages_to_flush: Vec, + /// Mapping of L1 transaction hash to a chronological sequence of generated L2 transactions. + pub l1_to_l2_tx_hashes: HashMap>, } impl MessagingBroker { @@ -142,7 +141,7 @@ impl Starknet { } for message in &messages { - let hash = format!("{}", message.hash()); + let hash = H256(*message.hash().as_bytes()); let count = self.messaging.l2_to_l1_messages_hashes.entry(hash).or_insert(0); *count += 1; } @@ -188,14 +187,14 @@ impl Starknet { // Ensure latest messages are collected before consuming the message. self.collect_messages_to_l1().await?; - let hash = format!("{}", message.hash()); - let count = self.messaging.l2_to_l1_messages_hashes.entry(hash.clone()).or_insert(0); + let hash = H256(*message.hash().as_bytes()); + let count = self.messaging.l2_to_l1_messages_hashes.entry(hash).or_insert(0); if *count > 0 { *count -= 1; Ok(message.hash()) } else { - Err(Error::MessagingError(MessagingError::MessageToL1NotPresent(hash))) + Err(Error::MessagingError(MessagingError::MessageToL1NotPresent(hash.to_string()))) } } diff --git a/crates/starknet-devnet-core/src/stack_trace.rs b/crates/starknet-devnet-core/src/stack_trace.rs new file mode 100644 index 000000000..b2eaf7bac --- /dev/null +++ b/crates/starknet-devnet-core/src/stack_trace.rs @@ -0,0 +1,438 @@ +// Copied with minor modifications from blockifier/src/execution/stack_trace.rs. +// Try removing once included in a blockifier release. + +use blockifier::execution::deprecated_syscalls::hint_processor::DeprecatedSyscallExecutionError; +use blockifier::execution::errors::{ + ConstructorEntryPointExecutionError, EntryPointExecutionError, +}; +use blockifier::execution::syscalls::hint_processor::SyscallExecutionError; +use blockifier::transaction::errors::TransactionExecutionError; +use cairo_vm::types::relocatable::Relocatable; +use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; +use cairo_vm::vm::errors::hint_errors::HintError; +use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; + +pub const TRACE_LENGTH_CAP: usize = 15000; +pub const TRACE_EXTRA_CHARS_SLACK: usize = 100; + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub enum PreambleType { + CallContract, + LibraryCall, + Constructor, +} + +impl PreambleType { + pub fn text(&self) -> &str { + match self { + Self::CallContract => "Error in the called contract", + Self::LibraryCall => "Error in a library call", + Self::Constructor => "Error in the contract class constructor", + } + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct EntryPointErrorFrame { + pub depth: usize, + pub preamble_type: PreambleType, + pub storage_address: ContractAddress, + pub class_hash: ClassHash, + pub selector: Option, +} + +impl EntryPointErrorFrame { + fn preamble_text(&self) -> String { + format!( + "{}: {} (contract address: {:#064x}, class hash: {:#064x}, selector: {}):", + self.depth, + self.preamble_type.text(), + self.storage_address.0.key(), + self.class_hash.0, + if let Some(selector) = self.selector { + format!("{:#064x}", selector.0) + } else { + "UNKNOWN".to_string() + } + ) + } +} + +impl From<&EntryPointErrorFrame> for String { + fn from(value: &EntryPointErrorFrame) -> Self { + value.preamble_text() + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct VmExceptionFrame { + pub pc: Relocatable, + pub error_attr_value: Option, + pub traceback: Option, +} + +impl From<&VmExceptionFrame> for String { + fn from(value: &VmExceptionFrame) -> Self { + let error_msg = match &value.error_attr_value { + Some(error_msg) => error_msg.clone(), + None => String::new(), + }; + let vm_exception_preamble = format!("Error at pc={}:", value.pc); + let vm_exception_traceback = if let Some(traceback) = &value.traceback { + format!("\n{}", traceback) + } else { + "".to_string() + }; + format!("{error_msg}{vm_exception_preamble}{vm_exception_traceback}") + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub enum Frame { + EntryPoint(EntryPointErrorFrame), + Vm(VmExceptionFrame), + StringFrame(String), +} + +impl From<&Frame> for String { + fn from(value: &Frame) -> Self { + match value { + Frame::EntryPoint(entry_point_frame) => entry_point_frame.into(), + Frame::Vm(vm_exception_frame) => vm_exception_frame.into(), + Frame::StringFrame(error) => error.clone(), + } + } +} + +impl From for Frame { + fn from(value: EntryPointErrorFrame) -> Self { + Frame::EntryPoint(value) + } +} + +impl From for Frame { + fn from(value: VmExceptionFrame) -> Self { + Frame::Vm(value) + } +} + +impl From for Frame { + fn from(value: String) -> Self { + Frame::StringFrame(value) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Default, Debug)] +pub struct ErrorStack { + pub stack: Vec, +} + +impl ErrorStack { + pub fn from_str_err(s: &str) -> Self { + Self { stack: vec![Frame::StringFrame(s.into())] } + } + + pub fn push(&mut self, frame: Frame) { + self.stack.push(frame); + } +} + +/// Extracts the error trace from a `TransactionExecutionError`. This is a top level function. +pub fn gen_tx_execution_error_trace(error: &TransactionExecutionError) -> ErrorStack { + match error { + TransactionExecutionError::ExecutionError { + error, + class_hash, + storage_address, + selector, + } + | TransactionExecutionError::ValidateTransactionError { + error, + class_hash, + storage_address, + selector, + } => gen_error_trace_from_entry_point_error( + error, + storage_address, + class_hash, + Some(selector), + PreambleType::CallContract, + ), + TransactionExecutionError::ContractConstructorExecutionFailed( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address: storage_address, + constructor_selector, + }, + ) => gen_error_trace_from_entry_point_error( + error, + storage_address, + class_hash, + constructor_selector.as_ref(), + PreambleType::Constructor, + ), + _ => { + // Top-level error is unrelated to Cairo execution, no "real" frames. + let mut stack = ErrorStack::default(); + stack.push(Frame::StringFrame(error.to_string())); + stack + } + } +} + +/// Generate error stack from top-level entry point execution error. +fn gen_error_trace_from_entry_point_error( + error: &EntryPointExecutionError, + storage_address: &ContractAddress, + class_hash: &ClassHash, + entry_point_selector: Option<&EntryPointSelector>, + preamble_type: PreambleType, +) -> ErrorStack { + let mut error_stack: ErrorStack = ErrorStack::default(); + let depth = 0; + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type, + storage_address: *storage_address, + class_hash: *class_hash, + selector: entry_point_selector.copied(), + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(&mut error_stack, depth + 1, error); + error_stack +} + +fn extract_cairo_run_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + error: &CairoRunError, +) { + if let CairoRunError::VmException(vm_exception) = error { + error_stack.push( + VmExceptionFrame { + pc: vm_exception.pc, + error_attr_value: vm_exception.error_attr_value.clone(), + traceback: vm_exception.traceback.clone(), + } + .into(), + ); + extract_virtual_machine_error_into_stack_trace(error_stack, depth, &vm_exception.inner_exc); + } else { + error_stack.push(error.to_string().into()); + } +} + +fn extract_virtual_machine_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + vm_error: &VirtualMachineError, +) { + match vm_error { + VirtualMachineError::Hint(ref boxed_hint_error) => { + if let HintError::Internal(internal_vm_error) = &boxed_hint_error.1 { + return extract_virtual_machine_error_into_stack_trace( + error_stack, + depth, + internal_vm_error, + ); + } + error_stack.push(boxed_hint_error.1.to_string().into()); + } + VirtualMachineError::Other(anyhow_error) => { + let syscall_exec_err = anyhow_error.downcast_ref::(); + if let Some(downcast_anyhow) = syscall_exec_err { + extract_syscall_execution_error_into_stack_trace( + error_stack, + depth, + downcast_anyhow, + ) + } else { + let deprecated_syscall_exec_err = + anyhow_error.downcast_ref::(); + if let Some(downcast_anyhow) = deprecated_syscall_exec_err { + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth, + downcast_anyhow, + ) + } + } + } + _ => { + error_stack.push(format!("{}\n", vm_error).into()); + } + } +} + +fn extract_syscall_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + syscall_error: &SyscallExecutionError, +) { + match syscall_error { + SyscallExecutionError::CallContractExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::CallContract, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error) + } + SyscallExecutionError::LibraryCallExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::LibraryCall, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error); + } + SyscallExecutionError::ConstructorEntryPointExecutionError( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address, + constructor_selector, + }, + ) => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::Constructor, + storage_address: *contract_address, + class_hash: *class_hash, + selector: *constructor_selector, + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(error_stack, depth, error) + } + SyscallExecutionError::EntryPointExecutionError(entry_point_error) => { + extract_entry_point_execution_error_into_stack_trace( + error_stack, + depth, + entry_point_error, + ) + } + _ => { + error_stack.push(syscall_error.to_string().into()); + } + } +} + +fn extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + syscall_error: &DeprecatedSyscallExecutionError, +) { + match syscall_error { + DeprecatedSyscallExecutionError::CallContractExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::CallContract, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth + 1, + error, + ) + } + DeprecatedSyscallExecutionError::LibraryCallExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::LibraryCall, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth + 1, + error, + ) + } + DeprecatedSyscallExecutionError::ConstructorEntryPointExecutionError( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address, + constructor_selector, + }, + ) => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::Constructor, + storage_address: *contract_address, + class_hash: *class_hash, + selector: *constructor_selector, + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(error_stack, depth, error) + } + DeprecatedSyscallExecutionError::EntryPointExecutionError(entry_point_error) => { + extract_entry_point_execution_error_into_stack_trace( + error_stack, + depth, + entry_point_error, + ) + } + _ => error_stack.push(syscall_error.to_string().into()), + } +} + +fn extract_entry_point_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + entry_point_error: &EntryPointExecutionError, +) { + match entry_point_error { + EntryPointExecutionError::CairoRunError(cairo_run_error) => { + extract_cairo_run_error_into_stack_trace(error_stack, depth, cairo_run_error) + } + _ => error_stack.push(format!("{}\n", entry_point_error).into()), + } +} diff --git a/crates/starknet-devnet-core/src/starknet/add_declare_transaction.rs b/crates/starknet-devnet-core/src/starknet/add_declare_transaction.rs index d003c2aee..2f347397f 100644 --- a/crates/starknet-devnet-core/src/starknet/add_declare_transaction.rs +++ b/crates/starknet-devnet-core/src/starknet/add_declare_transaction.rs @@ -1,4 +1,5 @@ use blockifier::transaction::transactions::ExecutableTransaction; +use starknet_types::compile_sierra_contract; use starknet_types::contract_class::ContractClass; use starknet_types::felt::{ClassHash, CompiledClassHash, TransactionHash}; use starknet_types::rpc::transactions::declare_transaction_v0v1::DeclareTransactionV0V1; @@ -11,14 +12,13 @@ use starknet_types::rpc::transactions::{ use crate::error::{DevnetResult, Error, TransactionValidationError}; use crate::starknet::Starknet; use crate::state::CustomState; -use crate::utils::calculate_casm_hash; pub fn add_declare_transaction( starknet: &mut Starknet, broadcasted_declare_transaction: BroadcastedDeclareTransaction, ) -> DevnetResult<(TransactionHash, ClassHash)> { if broadcasted_declare_transaction.is_max_fee_zero_value() { - return Err(TransactionValidationError::InsufficientMaxFee.into()); + return Err(TransactionValidationError::InsufficientResourcesForValidate.into()); } if broadcasted_declare_transaction.is_only_query() { @@ -108,16 +108,9 @@ fn assert_casm_hash_is_valid( match (contract_class, received_casm_hash) { (ContractClass::Cairo0(_), None) => Ok(()), // if cairo0, casm_hash expected to be None (ContractClass::Cairo1(cairo_lang_contract_class), Some(received_casm_hash)) => { - let casm_json = usc::compile_contract( - serde_json::to_value(cairo_lang_contract_class) - .map_err(|err| Error::SerializationError { origin: err.to_string() })?, - ) - .map_err(|err| { - let reason = err.to_string(); - Error::TypesError(starknet_types::error::Error::SierraCompilationError { reason }) - })?; - - let calculated_casm_hash = calculate_casm_hash(casm_json)?; + let casm = compile_sierra_contract(cairo_lang_contract_class)?; + + let calculated_casm_hash = casm.compiled_class_hash(); if calculated_casm_hash == received_casm_hash { Ok(()) } else { @@ -219,7 +212,9 @@ mod tests { assert!(result.is_err()); match result.err().unwrap() { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -242,7 +237,9 @@ mod tests { assert!(result.is_err()); match result.err().unwrap() { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -383,7 +380,9 @@ mod tests { assert!(result.is_err()); match result.err().unwrap() { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -402,7 +401,7 @@ mod tests { match starknet.add_declare_transaction(declare_txn).unwrap_err() { crate::error::Error::TransactionValidationError( - crate::error::TransactionValidationError::InsufficientMaxFee, + crate::error::TransactionValidationError::InsufficientResourcesForValidate, ) => {} err => { panic!("Wrong error type received {:?}", err); diff --git a/crates/starknet-devnet-core/src/starknet/add_deploy_account_transaction.rs b/crates/starknet-devnet-core/src/starknet/add_deploy_account_transaction.rs index ae1be0a24..71d32c429 100644 --- a/crates/starknet-devnet-core/src/starknet/add_deploy_account_transaction.rs +++ b/crates/starknet-devnet-core/src/starknet/add_deploy_account_transaction.rs @@ -16,7 +16,7 @@ pub fn add_deploy_account_transaction( broadcasted_deploy_account_transaction: BroadcastedDeployAccountTransaction, ) -> DevnetResult<(TransactionHash, ContractAddress)> { if broadcasted_deploy_account_transaction.is_max_fee_zero_value() { - return Err(TransactionValidationError::InsufficientMaxFee.into()); + return Err(TransactionValidationError::InsufficientResourcesForValidate.into()); } if broadcasted_deploy_account_transaction.is_only_query() { @@ -155,7 +155,9 @@ mod tests { assert!(result.is_err()); match result.err().unwrap() { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -172,7 +174,9 @@ mod tests { )) .unwrap_err(); match txn_err { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -265,7 +269,7 @@ mod tests { .unwrap_err() { Error::TransactionValidationError( - crate::error::TransactionValidationError::InsufficientMaxFee, + crate::error::TransactionValidationError::InsufficientResourcesForValidate, ) => {} err => { panic!("Wrong error type: {:?}", err); diff --git a/crates/starknet-devnet-core/src/starknet/add_invoke_transaction.rs b/crates/starknet-devnet-core/src/starknet/add_invoke_transaction.rs index 96d17c785..aa1028336 100644 --- a/crates/starknet-devnet-core/src/starknet/add_invoke_transaction.rs +++ b/crates/starknet-devnet-core/src/starknet/add_invoke_transaction.rs @@ -15,7 +15,7 @@ pub fn add_invoke_transaction( broadcasted_invoke_transaction: BroadcastedInvokeTransaction, ) -> DevnetResult { if broadcasted_invoke_transaction.is_max_fee_zero_value() { - return Err(TransactionValidationError::InsufficientMaxFee.into()); + return Err(TransactionValidationError::InsufficientResourcesForValidate.into()); } if broadcasted_invoke_transaction.is_only_query() { @@ -207,7 +207,9 @@ mod tests { .expect_err("Expected MaxFeeZeroError"); match invoke_v3_txn_error { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } @@ -277,7 +279,7 @@ mod tests { assert!(transaction.is_err()); match transaction.err().unwrap() { Error::TransactionValidationError( - TransactionValidationError::InsufficientMaxFee, + TransactionValidationError::InsufficientResourcesForValidate, ) => {} _ => { panic!("Wrong error type") @@ -379,7 +381,9 @@ mod tests { assert!(result.is_err()); match result.err().unwrap() { - Error::TransactionValidationError(TransactionValidationError::InsufficientMaxFee) => {} + Error::TransactionValidationError( + TransactionValidationError::InsufficientResourcesForValidate, + ) => {} _ => panic!("Wrong error type"), } } diff --git a/crates/starknet-devnet-core/src/starknet/add_l1_handler_transaction.rs b/crates/starknet-devnet-core/src/starknet/add_l1_handler_transaction.rs index 5f1c01a46..4a0c9dc66 100644 --- a/crates/starknet-devnet-core/src/starknet/add_l1_handler_transaction.rs +++ b/crates/starknet-devnet-core/src/starknet/add_l1_handler_transaction.rs @@ -1,4 +1,5 @@ use blockifier::transaction::transactions::ExecutableTransaction; +use ethers::types::H256; use starknet_types::felt::TransactionHash; use starknet_types::rpc::transactions::l1_handler_transaction::L1HandlerTransaction; use starknet_types::rpc::transactions::{Transaction, TransactionWithHash}; @@ -34,6 +35,17 @@ pub fn add_l1_handler_transaction( blockifier_execution_info, )?; + // If L1 tx hash present, store the generated L2 tx hash in its messaging entry. + // Not done as part of `handle_transaction_result` as it is specific to this tx type. + if let Some(l1_tx_hash) = transaction.l1_transaction_hash { + starknet + .messaging + .l1_to_l2_tx_hashes + .entry(H256(*l1_tx_hash.as_bytes())) + .or_default() + .push(transaction_hash); + } + Ok(transaction_hash) } @@ -42,8 +54,6 @@ mod tests { // Constants taken from test_estimate_message_fee.rs. const WHITELISTED_L1_ADDRESS: &str = "0x8359E4B0152ed5A731162D3c7B0D8D56edB165A0"; - use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError}; - use blockifier::transaction::errors::TransactionExecutionError::ExecutionError; use nonzero_ext::nonzero; use starknet_rs_core::types::{Felt, TransactionExecutionStatus, TransactionFinalityStatus}; use starknet_rs_core::utils::get_selector_from_name; @@ -60,6 +70,7 @@ mod tests { self, DEVNET_DEFAULT_CHAIN_ID, DEVNET_DEFAULT_STARTING_BLOCK_NUMBER, ETH_ERC20_CONTRACT_ADDRESS, STRK_ERC20_CONTRACT_ADDRESS, }; + use crate::stack_trace::Frame; use crate::starknet::{predeployed, Starknet}; use crate::state::CustomState; use crate::traits::{Deployed, HashIdentifiedMut}; @@ -130,17 +141,18 @@ mod tests { vec![Felt::from(11), Felt::from(9999)], ); - let result = starknet.add_l1_handler_transaction(transaction); - - match result { - Err(crate::error::Error::BlockifierTransactionError(ExecutionError { - error: - EntryPointExecutionError::PreExecutionError(PreExecutionError::EntryPointNotFound( - selector, - )), - .. - })) => { - assert_eq!(selector.0, withdraw_selector) + match starknet.add_l1_handler_transaction(transaction) { + Err(crate::error::Error::ContractExecutionError(error_stack)) => { + assert_eq!(error_stack.stack.len(), 2); + match &error_stack.stack[0] { + Frame::EntryPoint(entry_point_frame) => { + assert_eq!( + entry_point_frame.selector, + Some(starknet_api::core::EntryPointSelector(withdraw_selector)) + ) + } + other => panic!("Unexpected error frame: {other:?}"), + } } other => panic!("Wrong result: {other:?}"), } diff --git a/crates/starknet-devnet-core/src/starknet/estimations.rs b/crates/starknet-devnet-core/src/starknet/estimations.rs index c868720bc..d97b6cdba 100644 --- a/crates/starknet-devnet-core/src/starknet/estimations.rs +++ b/crates/starknet-devnet-core/src/starknet/estimations.rs @@ -12,6 +12,7 @@ use starknet_types::rpc::estimate_message_fee::{ use starknet_types::rpc::transactions::BroadcastedTransaction; use crate::error::{DevnetResult, Error}; +use crate::stack_trace::ErrorStack; use crate::starknet::Starknet; use crate::utils::get_versioned_constants; @@ -44,33 +45,32 @@ pub fn estimate_fee( let mut transactional_state = CachedState::create_transactional(&mut state.state); - transactions - .into_iter() - .enumerate() - .map(|(idx,(transaction, skip_validate_due_to_impersonation))| { - let estimate_fee_result = estimate_transaction_fee( - &mut transactional_state, - &block_context, - blockifier::transaction::transaction_execution::Transaction::AccountTransaction( - transaction, - ), - charge_fee, - skip_validate_due_to_impersonation.then_some(false).or(validate), /* if skip validate is true, then - * this means that this transaction - * has to skip validation, because - * the sender is impersonated. - * Otherwise use the validate parameter that is passed to the estimateFee request */ - return_error_on_reverted_execution - ); - - match estimate_fee_result { - Ok(estimated_fee) => Ok(estimated_fee), - // reverted transactions are failing with ExecutionError, but index is set to 0, so we override the index property - Err(Error::ExecutionError { execution_error , ..}) => Err(Error::ExecutionError { execution_error, index: idx }), - Err(err) => Err(Error::ExecutionError { execution_error: err.to_string(), index: idx }), + let mut estimations = vec![]; + for (tx_idx, (tx, skip_validate_due_to_impersonation)) in transactions.into_iter().enumerate() { + // If skip validate is true, this tx has to skip validation, because the sender is + // impersonated. Otherwise use the validate parameter passed to the estimateFee request. + let validate = skip_validate_due_to_impersonation.then_some(false).or(validate); + let estimation = estimate_transaction_fee( + &mut transactional_state, + &block_context, + blockifier::transaction::transaction_execution::Transaction::AccountTransaction(tx), + charge_fee, + validate, + return_error_on_reverted_execution, + ) + .map_err(|e| match e { + Error::ContractExecutionError(error_stack) => { + Error::ContractExecutionErrorInSimulation { failure_index: tx_idx, error_stack } } - }) - .collect() + other => Error::ContractExecutionErrorInSimulation { + failure_index: tx_idx, + error_stack: ErrorStack::from_str_err(&other.to_string()), + }, + })?; + estimations.push(estimation); + } + + Ok(estimations) } pub fn estimate_message_fee( @@ -130,7 +130,9 @@ fn estimate_transaction_fee( if let (true, Some(revert_error)) = (return_error_on_reverted_execution, transaction_execution_info.revert_error) { - return Err(Error::ExecutionError { execution_error: revert_error, index: 0 }); + // TODO until blockifier makes the actual stack trace available, we return the stringified + // error. The RPC spec would prefer a structured one, but a string is allowed. + return Err(Error::ContractExecutionError(ErrorStack::from_str_err(&revert_error))); } let gas_vector = transaction_execution_info diff --git a/crates/starknet-devnet-core/src/starknet/events.rs b/crates/starknet-devnet-core/src/starknet/events.rs index 34e2b263a..52145a043 100644 --- a/crates/starknet-devnet-core/src/starknet/events.rs +++ b/crates/starknet-devnet-core/src/starknet/events.rs @@ -86,7 +86,7 @@ pub(crate) fn get_events( /// * `address` - Optional. The address to filter the event by. /// * `keys_filter` - Optional. The keys to filter the event by. /// * `event` - The event to check if it applies to the filters. -fn check_if_filter_applies_for_event( +pub fn check_if_filter_applies_for_event( address: &Option, keys_filter: &Option>>, event: &Event, diff --git a/crates/starknet-devnet-core/src/starknet/get_class_impls.rs b/crates/starknet-devnet-core/src/starknet/get_class_impls.rs index 4052751e7..17de70bad 100644 --- a/crates/starknet-devnet-core/src/starknet/get_class_impls.rs +++ b/crates/starknet-devnet-core/src/starknet/get_class_impls.rs @@ -1,6 +1,8 @@ use blockifier::state::state_api::StateReader; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use starknet_api::block::BlockStatus; use starknet_rs_core::types::BlockId; +use starknet_types::compile_sierra_contract; use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::ContractClass; use starknet_types::felt::ClassHash; @@ -37,6 +39,7 @@ pub fn get_class_impl( BlockStatus::Rejected => return Err(Error::NoBlock), }; + // Returns sierra for cairo1; returns the only artifact for cairo0. match starknet.rpc_contract_classes.read().get_class(&class_hash, &block_number_or_pending) { Some(class) => Ok(class.clone()), None => Err(Error::StateError(StateError::NoneClassHash(class_hash))), @@ -52,6 +55,20 @@ pub fn get_class_at_impl( starknet.get_class(block_id, class_hash) } +pub fn get_compiled_casm_impl( + starknet: &Starknet, + block_id: &BlockId, + class_hash: ClassHash, +) -> DevnetResult { + let contract_class = get_class_impl(starknet, block_id, class_hash)?; + match contract_class { + ContractClass::Cairo1(sierra_contract_class) => { + Ok(compile_sierra_contract(&sierra_contract_class)?) + } + ContractClass::Cairo0(_) => Err(Error::StateError(StateError::NoneCasmClass(class_hash))), + } +} + #[cfg(test)] mod tests { use starknet_rs_core::types::BlockId; diff --git a/crates/starknet-devnet-core/src/starknet/mod.rs b/crates/starknet-devnet-core/src/starknet/mod.rs index 5f3815502..ea83296b5 100644 --- a/crates/starknet-devnet-core/src/starknet/mod.rs +++ b/crates/starknet-devnet-core/src/starknet/mod.rs @@ -7,17 +7,18 @@ use blockifier::execution::entry_point::CallEntryPoint; use blockifier::state::cached_state::CachedState; use blockifier::state::state_api::StateReader; use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::errors::TransactionExecutionError; use blockifier::transaction::objects::TransactionExecutionInfo; use blockifier::transaction::transactions::ExecutableTransaction; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use ethers::types::H256; use parking_lot::RwLock; use starknet_api::block::{BlockNumber, BlockStatus, BlockTimestamp, GasPrice, GasPricePerToken}; use starknet_api::core::SequencerContractAddress; use starknet_api::felt; use starknet_api::transaction::Fee; -use starknet_config::BlockGenerationOn; use starknet_rs_core::types::{ - BlockId, BlockTag, Call, ExecutionResult, Felt, MsgFromL1, TransactionExecutionStatus, - TransactionFinalityStatus, + BlockId, BlockTag, Call, ExecutionResult, Felt, Hash256, MsgFromL1, TransactionFinalityStatus, }; use starknet_rs_core::utils::get_selector_from_name; use starknet_rs_signers::Signer; @@ -46,8 +47,8 @@ use starknet_types::rpc::transactions::l1_handler_transaction::L1HandlerTransact use starknet_types::rpc::transactions::{ BlockTransactionTrace, BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, BroadcastedTransactionCommon, - SimulatedTransaction, SimulationFlag, TransactionTrace, TransactionType, TransactionWithHash, - TransactionWithReceipt, Transactions, + L1HandlerTransactionStatus, SimulatedTransaction, SimulationFlag, TransactionStatus, + TransactionTrace, TransactionType, TransactionWithHash, TransactionWithReceipt, Transactions, }; use starknet_types::traits::HashProducer; use tracing::{error, info}; @@ -70,6 +71,7 @@ use crate::error::{DevnetResult, Error, TransactionValidationError}; use crate::messaging::MessagingBroker; use crate::predeployed_accounts::PredeployedAccounts; use crate::raw_execution::RawExecutionV1; +use crate::stack_trace::{gen_tx_execution_error_trace, ErrorStack}; use crate::state::state_diff::StateDiff; use crate::state::{CommittedClassStorage, CustomState, CustomStateReader, StarknetState}; use crate::traits::{AccountGenerator, Deployed, HashIdentified, HashIdentifiedMut}; @@ -83,7 +85,7 @@ mod add_l1_handler_transaction; mod cheats; pub(crate) mod defaulter; mod estimations; -mod events; +pub mod events; mod get_class_impls; mod predeployed; pub mod starknet_config; @@ -406,7 +408,7 @@ impl Starknet { self.transactions.insert(transaction_hash, transaction_to_add); // create new block from pending one, only in block-generation-on-transaction mode - if self.config.block_generation_on == BlockGenerationOn::Transaction { + if !self.config.uses_pending_block() { self.generate_new_block_and_state()?; } @@ -598,6 +600,14 @@ impl Starknet { get_class_impls::get_class_at_impl(self, block_id, contract_address) } + pub fn get_compiled_casm( + &self, + block_id: &BlockId, + class_hash: ClassHash, + ) -> DevnetResult { + get_class_impls::get_compiled_casm_impl(self, block_id, class_hash) + } + pub fn call( &mut self, block_id: &BlockId, @@ -609,12 +619,15 @@ impl Starknet { let state = self.get_mut_state_at(block_id)?; state.assert_contract_deployed(ContractAddress::new(contract_address)?)?; + let storage_address = contract_address.try_into()?; + let class_hash = state.get_class_hash_at(storage_address)?; let call = CallEntryPoint { calldata: starknet_api::transaction::Calldata(std::sync::Arc::new(calldata.clone())), storage_address: contract_address.try_into()?, entry_point_selector: starknet_api::core::EntryPointSelector(entrypoint_selector), initial_gas: block_context.versioned_constants().tx_initial_gas(), + class_hash: Some(class_hash), ..Default::default() }; @@ -631,11 +644,18 @@ impl Starknet { )?; let mut transactional_state = CachedState::create_transactional(&mut state.state); - let res = call.execute( - &mut transactional_state, - &mut Default::default(), - &mut execution_context, - )?; + let res = call + .execute(&mut transactional_state, &mut Default::default(), &mut execution_context) + .map_err(|error| { + Error::ContractExecutionError(gen_tx_execution_error_trace( + &TransactionExecutionError::ExecutionError { + error, + class_hash, + storage_address, + selector: starknet_api::core::EntryPointSelector(entrypoint_selector), + }, + )) + })?; Ok(res.execution.retdata.0) } @@ -1013,6 +1033,17 @@ impl Starknet { .ok_or(Error::NoTransaction) } + pub fn get_unlimited_events( + &self, + from_block: Option, + to_block: Option, + address: Option, + keys: Option>>, + ) -> DevnetResult> { + events::get_events(self, from_block, to_block, address, keys, 0, None) + .map(|(emitted_events, _)| emitted_events) + } + pub fn get_events( &self, from_block: Option, @@ -1070,10 +1101,9 @@ impl Starknet { pub fn get_transaction_execution_and_finality_status( &self, transaction_hash: TransactionHash, - ) -> DevnetResult<(TransactionExecutionStatus, TransactionFinalityStatus)> { + ) -> DevnetResult { let transaction = self.transactions.get(&transaction_hash).ok_or(Error::NoTransaction)?; - - Ok((transaction.execution_result.status(), transaction.finality_status)) + Ok(transaction.get_status()) } pub fn simulate_transactions( @@ -1104,16 +1134,18 @@ impl Starknet { transactions .iter() .enumerate() - .map(|(idx, txn)| { + .map(|(tx_idx, txn)| { // According to this conversation https://spaceshard.slack.com/archives/C03HL8DH52N/p1710683496750409, simulating a transaction will: // fail if the fee provided is 0 // succeed if the fee provided is 0 and SKIP_FEE_CHARGE is set // succeed if the fee provided is > 0 if txn.is_max_fee_zero_value() && !skip_fee_charge { - return Err(Error::ExecutionError { - execution_error: TransactionValidationError::InsufficientMaxFee - .to_string(), - index: idx, + return Err(Error::ContractExecutionErrorInSimulation { + failure_index: tx_idx, + error_stack: ErrorStack::from_str_err( + &TransactionValidationError::InsufficientResourcesForValidate + .to_string(), + ), }); } @@ -1133,19 +1165,19 @@ impl Starknet { let mut transactional_state = CachedState::new(CachedState::create_transactional(&mut state.state)); - for (idx, (blockifier_transaction, transaction_type, skip_validate_due_to_impersonation)) in + for (tx_idx, (blockifier_tx, transaction_type, skip_validate_due_to_impersonation)) in blockifier_transactions.into_iter().enumerate() { - let tx_execution_info = blockifier_transaction + let tx_execution_info = blockifier_tx .execute( &mut transactional_state, &block_context, !skip_fee_charge, !(skip_validate || skip_validate_due_to_impersonation), ) - .map_err(|err| Error::ExecutionError { - execution_error: Error::from(err).to_string(), - index: idx, + .map_err(|e| Error::ContractExecutionErrorInSimulation { + failure_index: tx_idx, + error_stack: gen_tx_execution_error_trace(&e), })?; let block_number = block_context.block_info().block_number.0; @@ -1338,6 +1370,30 @@ impl Starknet { Ok(false) } } + + pub fn get_messages_status( + &self, + l1_tx_hash: Hash256, + ) -> Option> { + match self.messaging.l1_to_l2_tx_hashes.get(&H256(*l1_tx_hash.as_bytes())) { + Some(l2_tx_hashes) => { + let mut statuses = vec![]; + for l2_tx_hash in l2_tx_hashes { + match self.transactions.get(l2_tx_hash) { + Some(l2_tx) => statuses.push(L1HandlerTransactionStatus { + transaction_hash: *l2_tx_hash, + finality_status: l2_tx.finality_status, + failure_reason: l2_tx.execution_info.revert_error.clone(), + }), + // should never happen due to handling in add_l1_handler_transaction + None => return None, + } + } + Some(statuses) + } + None => None, + } + } } #[cfg(test)] @@ -1345,11 +1401,9 @@ mod tests { use std::thread; use std::time::Duration; - use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError}; use blockifier::state::state_api::{State, StateReader}; use nonzero_ext::nonzero; use starknet_api::block::{BlockHash, BlockNumber, BlockStatus, BlockTimestamp, GasPrice}; - use starknet_api::core::EntryPointSelector; use starknet_rs_core::types::{BlockId, BlockTag, Felt}; use starknet_rs_core::utils::get_selector_from_name; use starknet_types::contract_address::ContractAddress; @@ -1366,6 +1420,7 @@ mod tests { STRK_ERC20_CONTRACT_ADDRESS, }; use crate::error::{DevnetResult, Error}; + use crate::stack_trace::{ErrorStack, Frame}; use crate::starknet::starknet_config::{StarknetConfig, StateArchiveCapacity}; use crate::traits::{Accounted, Deployed, HashIdentified}; use crate::utils::test_utils::{ @@ -1659,9 +1714,22 @@ mod tests { entry_point_selector, vec![Felt::from(predeployed_account.account_address)], ) { - Err(Error::BlockifierExecutionError(EntryPointExecutionError::PreExecutionError( - PreExecutionError::EntryPointNotFound(EntryPointSelector(missing_selector)), - ))) => assert_eq!(missing_selector, entry_point_selector), + Err(Error::ContractExecutionError(ErrorStack { stack })) => match &stack[..] { + [Frame::EntryPoint(entry_point_frame), Frame::StringFrame(error_msg)] => { + assert_eq!( + entry_point_frame.selector, + Some(starknet_api::core::EntryPointSelector(entry_point_selector)) + ); + assert_eq!( + error_msg, + &format!( + "Entry point EntryPointSelector({}) not found in contract.\n", + entry_point_selector.to_hex_string() + ) + ); + } + _ => panic!("Unexpected error stack: {stack:?}"), + }, unexpected => panic!("Should have failed; got {unexpected:?}"), } } diff --git a/crates/starknet-devnet-core/src/starknet/starknet_config.rs b/crates/starknet-devnet-core/src/starknet/starknet_config.rs index face8345b..86f2bee55 100644 --- a/crates/starknet-devnet-core/src/starknet/starknet_config.rs +++ b/crates/starknet-devnet-core/src/starknet/starknet_config.rs @@ -125,6 +125,15 @@ pub struct StarknetConfig { pub strk_erc20_contract_class: String, } +impl StarknetConfig { + pub fn uses_pending_block(&self) -> bool { + match self.block_generation_on { + BlockGenerationOn::Transaction => false, + BlockGenerationOn::Demand | BlockGenerationOn::Interval(_) => true, + } + } +} + #[allow(clippy::unwrap_used)] impl Default for StarknetConfig { fn default() -> Self { diff --git a/crates/starknet-devnet-core/src/starknet/state_update.rs b/crates/starknet-devnet-core/src/starknet/state_update.rs index 087af95da..d1f60a0ae 100644 --- a/crates/starknet-devnet-core/src/starknet/state_update.rs +++ b/crates/starknet-devnet-core/src/starknet/state_update.rs @@ -21,6 +21,7 @@ mod tests { use starknet_rs_core::types::{ BlockId, BlockTag, Felt, TransactionExecutionStatus, TransactionFinalityStatus, }; + use starknet_types::compile_sierra_contract; use starknet_types::contract_class::ContractClass; use starknet_types::rpc::state::{ClassHashPair, ContractNonce, ThinStateDiff}; use starknet_types::rpc::transactions::broadcasted_declare_transaction_v2::BroadcastedDeclareTransactionV2; @@ -28,7 +29,6 @@ mod tests { use crate::starknet::tests::setup_starknet_with_no_signature_check_account; use crate::traits::HashIdentifiedMut; - use crate::utils::calculate_casm_hash; use crate::utils::test_utils::dummy_cairo_1_contract_class; #[test] @@ -36,15 +36,11 @@ mod tests { fn correct_state_update_after_declare_transaction_v2() { let (mut starknet, acc) = setup_starknet_with_no_signature_check_account(1e18 as u128); let contract_class = dummy_cairo_1_contract_class(); - + let compiled_class_hash = + compile_sierra_contract(&contract_class).unwrap().compiled_class_hash(); let sierra_class_hash = ContractClass::Cairo1(contract_class.clone()).generate_hash().unwrap(); - let casm_contract_class_json = - usc::compile_contract(serde_json::to_value(contract_class.clone()).unwrap()).unwrap(); - - let compiled_class_hash = calculate_casm_hash(casm_contract_class_json).unwrap(); - let declare_txn = BroadcastedDeclareTransactionV2::new( &contract_class, compiled_class_hash, diff --git a/crates/starknet-devnet-core/src/state/mod.rs b/crates/starknet-devnet-core/src/state/mod.rs index 3d0c4737f..436322752 100644 --- a/crates/starknet-devnet-core/src/state/mod.rs +++ b/crates/starknet-devnet-core/src/state/mod.rs @@ -6,6 +6,7 @@ use blockifier::state::state_api::{State, StateReader}; use parking_lot::RwLock; use starknet_api::core::CompiledClassHash; use starknet_rs_core::types::Felt; +use starknet_types::compile_sierra_contract; use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::ContractClass; use starknet_types::felt::ClassHash; @@ -14,7 +15,6 @@ use self::state_diff::StateDiff; use self::state_readers::DictState; use crate::error::{DevnetResult, Error}; use crate::starknet::defaulter::StarknetDefaulter; -use crate::utils::calculate_casm_hash; pub(crate) mod state_diff; pub(crate) mod state_readers; @@ -376,19 +376,13 @@ impl CustomState for StarknetState { let class_hash = starknet_api::core::ClassHash(class_hash); if let ContractClass::Cairo1(cairo_lang_contract_class) = &contract_class { - let casm_json = usc::compile_contract( - serde_json::to_value(cairo_lang_contract_class) - .map_err(|err| Error::SerializationError { origin: err.to_string() })?, - ) - .map_err(|err| { - Error::TypesError(starknet_types::error::Error::SierraCompilationError { - reason: err.to_string(), - }) - })?; - - let casm_hash = starknet_api::core::CompiledClassHash(calculate_casm_hash(casm_json)?); + let casm_hash = + compile_sierra_contract(cairo_lang_contract_class)?.compiled_class_hash(); - self.state.state.set_compiled_class_hash(class_hash, casm_hash)?; + self.state.state.set_compiled_class_hash( + class_hash, + starknet_api::core::CompiledClassHash(casm_hash), + )?; }; self.state.state.set_contract_class(class_hash, compiled_class)?; diff --git a/crates/starknet-devnet-core/src/state/state_diff.rs b/crates/starknet-devnet-core/src/state/state_diff.rs index c54a08bde..7614b7919 100644 --- a/crates/starknet-devnet-core/src/state/state_diff.rs +++ b/crates/starknet-devnet-core/src/state/state_diff.rs @@ -183,6 +183,7 @@ mod tests { use starknet_api::transaction::Fee; use starknet_rs_core::types::{BlockId, BlockTag, Felt}; use starknet_rs_core::utils::get_selector_from_name; + use starknet_types::compile_sierra_contract; use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::ContractClass; use starknet_types::felt::felt_from_prefixed_hex; @@ -199,7 +200,6 @@ mod tests { use crate::starknet::Starknet; use crate::state::{CustomState, StarknetState}; use crate::traits::Deployed; - use crate::utils::calculate_casm_hash; use crate::utils::exported_test_utils::dummy_cairo_0_contract_class; use crate::utils::test_utils::{ cairo_0_account_without_validations, dummy_cairo_1_contract_class, dummy_contract_address, @@ -332,10 +332,8 @@ mod tests { for (contract_class, nonce) in [(replaceable_contract.clone(), Felt::ZERO), (events_contract.clone(), Felt::ONE)] { - let casm_contract_class_json = - usc::compile_contract(serde_json::to_value(contract_class.clone()).unwrap()) - .unwrap(); - let compiled_class_hash = calculate_casm_hash(casm_contract_class_json).unwrap(); + let compiled_class_hash = + compile_sierra_contract(&contract_class).unwrap().compiled_class_hash(); starknet .add_declare_transaction( diff --git a/crates/starknet-devnet-core/src/transactions.rs b/crates/starknet-devnet-core/src/transactions.rs index 7527a9b48..086a62439 100644 --- a/crates/starknet-devnet-core/src/transactions.rs +++ b/crates/starknet-devnet-core/src/transactions.rs @@ -14,8 +14,8 @@ use starknet_types::rpc::transaction_receipt::{ DeployTransactionReceipt, FeeAmount, FeeInUnits, TransactionReceipt, }; use starknet_types::rpc::transactions::{ - DeclareTransaction, DeployAccountTransaction, InvokeTransaction, Transaction, TransactionTrace, - TransactionType, TransactionWithHash, + DeclareTransaction, DeployAccountTransaction, InvokeTransaction, Transaction, + TransactionStatus, TransactionTrace, TransactionType, TransactionWithHash, }; use crate::constants::UDC_CONTRACT_ADDRESS; @@ -228,6 +228,18 @@ impl StarknetTransaction { } } + pub fn get_block_number(&self) -> Option { + self.block_number + } + + pub fn get_status(&self) -> TransactionStatus { + TransactionStatus { + finality_status: self.finality_status, + failure_reason: self.execution_info.revert_error.clone(), + execution_status: self.execution_result.status(), + } + } + pub fn get_trace(&self) -> Option { self.trace.clone() } diff --git a/crates/starknet-devnet-core/src/utils.rs b/crates/starknet-devnet-core/src/utils.rs index 32bf51e3e..12fb8802f 100644 --- a/crates/starknet-devnet-core/src/utils.rs +++ b/crates/starknet-devnet-core/src/utils.rs @@ -1,11 +1,9 @@ use blockifier::bouncer::{BouncerConfig, BouncerWeights, BuiltinCount}; use blockifier::versioned_constants::VersionedConstants; -use serde_json::Value; -use starknet_rs_core::types::contract::CompiledClass; use starknet_rs_core::types::Felt; use starknet_types::patricia_key::{PatriciaKey, StorageKey}; -use crate::error::{DevnetResult, Error}; +use crate::error::DevnetResult; pub mod random_number_generator { use rand::{thread_rng, Rng, SeedableRng}; @@ -70,21 +68,12 @@ pub(crate) fn custom_bouncer_config() -> BouncerConfig { } } -/// Returns the hash of a compiled class. -/// # Arguments -/// * `casm_json` - The compiled class in JSON format. -pub fn calculate_casm_hash(casm_json: Value) -> DevnetResult { - serde_json::from_value::(casm_json) - .map_err(|err| Error::DeserializationError { origin: err.to_string() })? - .class_hash() - .map_err(|err| Error::UnexpectedInternalError { msg: err.to_string() }) -} - #[cfg(test)] pub(crate) mod test_utils { use cairo_lang_starknet_classes::contract_class::ContractClass as SierraContractClass; use starknet_api::transaction::Fee; use starknet_rs_core::types::Felt; + use starknet_types::compile_sierra_contract; use starknet_types::contract_address::ContractAddress; use starknet_types::contract_class::{Cairo0ContractClass, Cairo0Json, ContractClass}; use starknet_types::rpc::transactions::broadcasted_declare_transaction_v1::BroadcastedDeclareTransactionV1; @@ -97,7 +86,6 @@ pub(crate) mod test_utils { }; use starknet_types::traits::HashProducer; - use super::calculate_casm_hash; use crate::constants::DEVNET_DEFAULT_CHAIN_ID; use crate::utils::exported_test_utils::dummy_cairo_0_contract_class; @@ -149,11 +137,8 @@ pub(crate) mod test_utils { sender_address: &ContractAddress, ) -> BroadcastedDeclareTransactionV2 { let contract_class = dummy_cairo_1_contract_class(); - - let casm_contract_class_json = - usc::compile_contract(serde_json::to_value(contract_class.clone()).unwrap()).unwrap(); - - let compiled_class_hash = calculate_casm_hash(casm_contract_class_json).unwrap(); + let compiled_class_hash = + compile_sierra_contract(&contract_class).unwrap().compiled_class_hash(); BroadcastedDeclareTransactionV2::new( &contract_class, diff --git a/crates/starknet-devnet-server/Cargo.toml b/crates/starknet-devnet-server/Cargo.toml index 2a35450a3..4a15168fc 100644 --- a/crates/starknet-devnet-server/Cargo.toml +++ b/crates/starknet-devnet-server/Cargo.toml @@ -31,6 +31,7 @@ thiserror = { workspace = true } anyhow = { workspace = true } lazy_static = { workspace = true } enum-helper-macros = { workspace = true } +rand = { workspace = true } # devnet starknet-core = { workspace = true } @@ -38,7 +39,6 @@ starknet-types = { workspace = true } starknet-rs-core = { workspace = true } [dev-dependencies] -rand = { workspace = true } rand_chacha = { workspace = true } regex_generate = { workspace = true } serde_yaml = { workspace = true } diff --git a/crates/starknet-devnet-server/src/api/http/endpoints/mint_token.rs b/crates/starknet-devnet-server/src/api/http/endpoints/mint_token.rs index bcedab0d6..7ddf9cf50 100644 --- a/crates/starknet-devnet-server/src/api/http/endpoints/mint_token.rs +++ b/crates/starknet-devnet-server/src/api/http/endpoints/mint_token.rs @@ -35,13 +35,12 @@ pub fn get_balance( [new_balance_low, new_balance_high] => Ok(join_felts(new_balance_high, new_balance_low)), _ => { let msg = format!( - "Fee token contract expected to return 2 values; got: {:?}", - new_balance_raw + "Fee token contract expected to return 2 values; got: {new_balance_raw:?}", ); - Err(ApiError::ContractError { - error: starknet_core::error::Error::UnexpectedInternalError { msg }, - }) + Err(ApiError::StarknetDevnetError( + starknet_core::error::Error::UnexpectedInternalError { msg }, + )) } } } diff --git a/crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs b/crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs index 230bd9d9a..c240d4eba 100644 --- a/crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs +++ b/crates/starknet-devnet-server/src/api/json_rpc/endpoints.rs @@ -1,4 +1,5 @@ use starknet_core::error::{Error, StateError}; +use starknet_core::stack_trace::ErrorStack; use starknet_rs_core::types::{BlockId as ImportedBlockId, MsgFromL1}; use starknet_types::contract_address::ContractAddress; use starknet_types::felt::{ClassHash, TransactionHash}; @@ -13,7 +14,9 @@ use starknet_types::rpc::transactions::{ use starknet_types::starknet_api::block::BlockStatus; use super::error::{ApiError, StrictRpcResult}; -use super::models::{BlockHashAndNumberOutput, SyncingOutput, TransactionStatusOutput}; +use super::models::{ + BlockHashAndNumberOutput, GetStorageProofInput, L1TransactionHashInput, SyncingOutput, +}; use super::{DevnetResponse, JsonRpcHandler, JsonRpcResponse, StarknetResponse, RPC_SPEC_VERSION}; use crate::api::http::endpoints::accounts::{ get_account_balance_impl, get_predeployed_accounts_impl, BalanceQuery, PredeployedAccountsQuery, @@ -22,7 +25,7 @@ use crate::api::http::endpoints::DevnetConfig; const DEFAULT_CONTINUATION_TOKEN: &str = "0"; -/// here are the definitions and stub implementations of all JSON-RPC read endpoints +/// The definitions of JSON-RPC read endpoints defined in starknet_api_openrpc.json impl JsonRpcHandler { /// starknet_specVersion pub fn spec_version(&self) -> StrictRpcResult { @@ -135,6 +138,16 @@ impl JsonRpcHandler { Ok(StarknetResponse::Felt(felt).into()) } + /// starknet_getStorageProof + pub async fn get_storage_proof(&self, data: GetStorageProofInput) -> StrictRpcResult { + match self.api.starknet.lock().await.get_block(data.block_id.as_ref()) { + // storage proofs not applicable to Devnet + Ok(_) => Err(ApiError::StorageProofNotSupported), + Err(Error::NoBlock) => Err(ApiError::BlockNotFound), + Err(unknown_error) => Err(ApiError::StarknetDevnetError(unknown_error)), + } + } + /// starknet_getTransactionByHash pub async fn get_transaction_by_hash( &self, @@ -159,13 +172,7 @@ impl JsonRpcHandler { .await .get_transaction_execution_and_finality_status(transaction_hash) { - Ok((execution_status, finality_status)) => { - Ok(StarknetResponse::TransactionStatusByHash(TransactionStatusOutput { - execution_status, - finality_status, - }) - .into()) - } + Ok(tx_status) => Ok(StarknetResponse::TransactionStatusByHash(tx_status).into()), Err(Error::NoTransaction) => Err(ApiError::TransactionNotFound), Err(err) => Err(err.into()), } @@ -222,6 +229,25 @@ impl JsonRpcHandler { } } + pub async fn get_compiled_casm( + &self, + block_id: BlockId, + class_hash: ClassHash, + ) -> StrictRpcResult { + // starknet_getCompiledCasm compiles sierra to casm the same way it is done in + // starknet_addDeclareTransaction, so if during starknet_addDeclareTransaction compilation + // does not fail, so it will not fail during this endpoint execution + match self.api.starknet.lock().await.get_compiled_casm(block_id.as_ref(), class_hash) { + Ok(compiled_casm) => Ok(StarknetResponse::CompiledCasm(compiled_casm).into()), + Err(e) => Err(match e { + Error::NoBlock => ApiError::BlockNotFound, + Error::StateError(_) => ApiError::ClassHashNotFound, + e @ Error::NoStateAtBlock { .. } => ApiError::NoStateAtBlock { msg: e.to_string() }, + unknown_error => ApiError::StarknetDevnetError(unknown_error), + }), + } + } + /// starknet_getClassAt pub async fn get_class_at( &self, @@ -291,7 +317,12 @@ impl JsonRpcHandler { Err(e @ Error::NoStateAtBlock { .. }) => { Err(ApiError::NoStateAtBlock { msg: e.to_string() }) } - Err(err) => Err(ApiError::ContractError { error: err }), + Err(Error::ContractExecutionError(error_stack)) => { + Err(ApiError::ContractError { error_stack }) + } + Err(e) => Err(ApiError::ContractError { + error_stack: ErrorStack::from_str_err(&e.to_string()), + }), } } @@ -310,10 +341,12 @@ impl JsonRpcHandler { Err(e @ Error::NoStateAtBlock { .. }) => { Err(ApiError::NoStateAtBlock { msg: e.to_string() }) } - Err(Error::ExecutionError { execution_error, index }) => { - Err(ApiError::ExecutionError { execution_error, index }) + Err(Error::ContractExecutionErrorInSimulation { failure_index, error_stack }) => { + Err(ApiError::TransactionExecutionError { failure_index, error_stack }) } - Err(err) => Err(err.into()), + Err(e) => Err(ApiError::ContractError { + error_stack: ErrorStack::from_str_err(&e.to_string()), + }), } } @@ -329,7 +362,12 @@ impl JsonRpcHandler { Err(e @ Error::NoStateAtBlock { .. }) => { Err(ApiError::NoStateAtBlock { msg: e.to_string() }) } - Err(err) => Err(ApiError::ContractError { error: err }), + Err(Error::ContractExecutionError(error)) => { + Err(ApiError::ContractError { error_stack: error }) + } + Err(e) => Err(ApiError::ContractError { + error_stack: ErrorStack::from_str_err(&e.to_string()), + }), } } @@ -431,19 +469,20 @@ impl JsonRpcHandler { ) -> StrictRpcResult { // borrowing as write/mutable because trace calculation requires so let mut starknet = self.api.starknet.lock().await; - let res = - starknet.simulate_transactions(block_id.as_ref(), &transactions, simulation_flags); - match res { + + match starknet.simulate_transactions(block_id.as_ref(), &transactions, simulation_flags) { Ok(result) => Ok(StarknetResponse::SimulateTransactions(result).into()), Err(Error::ContractNotFound) => Err(ApiError::ContractNotFound), Err(Error::NoBlock) => Err(ApiError::BlockNotFound), Err(e @ Error::NoStateAtBlock { .. }) => { Err(ApiError::NoStateAtBlock { msg: e.to_string() }) } - Err(Error::ExecutionError { execution_error, index }) => { - Err(ApiError::ExecutionError { execution_error, index }) + Err(Error::ContractExecutionErrorInSimulation { failure_index, error_stack }) => { + Err(ApiError::TransactionExecutionError { failure_index, error_stack }) } - Err(err) => Err(err.into()), + Err(e) => Err(ApiError::ContractError { + error_stack: ErrorStack::from_str_err(&e.to_string()), + }), } } @@ -471,6 +510,18 @@ impl JsonRpcHandler { } } + /// starknet_getMessagesStatus + pub async fn get_messages_status( + &self, + L1TransactionHashInput { transaction_hash }: L1TransactionHashInput, + ) -> StrictRpcResult { + let starknet = self.api.starknet.lock().await; + match starknet.get_messages_status(transaction_hash) { + Some(statuses) => Ok(StarknetResponse::MessagesStatusByL1Hash(statuses).into()), + None => Err(ApiError::TransactionNotFound), + } + } + /// devnet_getPredeployedAccounts pub async fn get_predeployed_accounts( &self, diff --git a/crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs b/crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs new file mode 100644 index 000000000..3dba73b18 --- /dev/null +++ b/crates/starknet-devnet-server/src/api/json_rpc/endpoints_ws.rs @@ -0,0 +1,320 @@ +use starknet_core::error::Error; +use starknet_rs_core::types::{BlockId, BlockTag}; +use starknet_types::rpc::block::{BlockResult, PendingBlock}; +use starknet_types::rpc::transactions::{TransactionWithHash, Transactions}; +use starknet_types::starknet_api::block::{BlockNumber, BlockStatus}; + +use super::error::ApiError; +use super::models::{ + BlockIdInput, EventsSubscriptionInput, PendingTransactionsSubscriptionInput, + SubscriptionIdInput, TransactionBlockInput, +}; +use super::{JsonRpcHandler, JsonRpcSubscriptionRequest}; +use crate::rpc_core::request::Id; +use crate::subscribe::{ + AddressFilter, NewTransactionStatus, PendingTransactionNotification, SocketId, Subscription, + SubscriptionNotification, TransactionHashWrapper, +}; + +fn disallow_pending_block(block_id: &BlockId) -> Result<(), ApiError> { + if let BlockId::Tag(BlockTag::Pending) = block_id { + Err(ApiError::CallOnPending) + } else { + Ok(()) + } +} + +/// The definitions of JSON-RPC read endpoints defined in starknet_ws_api.json +impl JsonRpcHandler { + pub async fn execute_ws( + &self, + request: JsonRpcSubscriptionRequest, + rpc_request_id: Id, + socket_id: SocketId, + ) -> Result<(), ApiError> { + match request { + JsonRpcSubscriptionRequest::NewHeads(data) => { + self.subscribe_new_heads(data, rpc_request_id, socket_id).await + } + JsonRpcSubscriptionRequest::TransactionStatus(data) => { + self.subscribe_tx_status(data, rpc_request_id, socket_id).await + } + JsonRpcSubscriptionRequest::PendingTransactions(data) => { + self.subscribe_pending_txs(data, rpc_request_id, socket_id).await + } + JsonRpcSubscriptionRequest::Events(data) => { + self.subscribe_events(data, rpc_request_id, socket_id).await + } + JsonRpcSubscriptionRequest::Unsubscribe(SubscriptionIdInput { subscription_id }) => { + let mut sockets = self.api.sockets.lock().await; + let socket_context = sockets.get_mut(&socket_id)?; + socket_context.unsubscribe(rpc_request_id, subscription_id).await + } + } + } + + /// Returns (starting block number, latest block number). Returns an error in case the starting + /// block does not exist or there are too many blocks. + async fn get_validated_block_number_range( + &self, + mut starting_block_id: BlockId, + ) -> Result<(u64, u64), ApiError> { + let starknet = self.api.starknet.lock().await; + + // Convert pending to latest to prevent getting block_number = 0 + starting_block_id = match starting_block_id { + BlockId::Tag(BlockTag::Pending) => BlockId::Tag(BlockTag::Latest), + other => other, + }; + + // checking the block's existence; aborted blocks treated as not found + let query_block = match starknet.get_block(&starting_block_id) { + Ok(block) => match block.status() { + BlockStatus::Rejected => Err(ApiError::BlockNotFound), + _ => Ok(block), + }, + Err(Error::NoBlock) => Err(ApiError::BlockNotFound), + Err(other) => Err(ApiError::StarknetDevnetError(other)), + }?; + + let latest_block = starknet.get_block(&BlockId::Tag(BlockTag::Latest))?; + + let query_block_number = query_block.block_number().0; + let latest_block_number = latest_block.block_number().0; + + // safe to subtract, ensured by previous checks + if latest_block_number - query_block_number > 1024 { + return Err(ApiError::TooManyBlocksBack); + } + + Ok((query_block_number, latest_block_number)) + } + + /// starknet_subscribeNewHeads + /// Checks if an optional block ID is provided. Validates that the block exists and is not too + /// many blocks in the past. If it is a valid block, the user is notified of all blocks from the + /// old up to the latest, and subscribed to new ones. If no block ID specified, the user is just + /// subscribed to new blocks. + async fn subscribe_new_heads( + &self, + block_input: Option, + rpc_request_id: Id, + socket_id: SocketId, + ) -> Result<(), ApiError> { + let block_id = if let Some(BlockIdInput { block_id }) = block_input { + block_id.into() + } else { + // if no block ID input, this eventually just subscribes the user to new blocks + BlockId::Tag(BlockTag::Latest) + }; + + disallow_pending_block(&block_id)?; + let (query_block_number, latest_block_number) = + self.get_validated_block_number_range(block_id).await?; + + // perform the actual subscription + let mut sockets = self.api.sockets.lock().await; + let socket_context = sockets.get_mut(&socket_id)?; + let subscription_id = + socket_context.subscribe(rpc_request_id, Subscription::NewHeads).await; + + if let BlockId::Tag(_) = block_id { + // if the specified block ID is a tag (i.e. latest/pending), no old block handling + return Ok(()); + } + + // Notifying of old blocks. latest_block_number inclusive? + // Yes, only if block_id != latest/pending (handled above) + let starknet = self.api.starknet.lock().await; + for block_n in query_block_number..=latest_block_number { + let old_block = starknet + .get_block(&BlockId::Number(block_n)) + .map_err(ApiError::StarknetDevnetError)?; + + let old_header = Box::new(old_block.into()); + let notification = SubscriptionNotification::NewHeads(old_header); + socket_context.notify(subscription_id, notification).await; + } + + Ok(()) + } + + /// Based on pending block usage and specified block ID, decide on subscription's sensitivity: + /// notify of changes in pending or latest block + fn get_subscription_tag(&self, block_id: BlockId) -> BlockTag { + if self.starknet_config.uses_pending_block() { + match block_id { + BlockId::Tag(tag) => tag, + BlockId::Hash(_) | BlockId::Number(_) => BlockTag::Pending, + } + } else { + BlockTag::Latest + } + } + + async fn get_pending_txs(&self) -> Result, ApiError> { + let starknet = self.api.starknet.lock().await; + let block = starknet.get_block_with_transactions(&BlockId::Tag(BlockTag::Pending))?; + match block { + BlockResult::PendingBlock(PendingBlock { + transactions: Transactions::Full(txs), + .. + }) => Ok(txs), + _ => { + // Never reached if get_block_with_transactions properly implemented. + Err(ApiError::StarknetDevnetError(Error::UnexpectedInternalError { + msg: "Invalid block".into(), + })) + } + } + } + + /// Does not return TOO_MANY_ADDRESSES_IN_FILTER + pub async fn subscribe_pending_txs( + &self, + maybe_subscription_input: Option, + rpc_request_id: Id, + socket_id: SocketId, + ) -> Result<(), ApiError> { + let with_details = maybe_subscription_input + .as_ref() + .and_then(|subscription_input| subscription_input.transaction_details) + .unwrap_or_default(); + + let address_filter = AddressFilter::new( + maybe_subscription_input + .and_then(|subscription_input| subscription_input.sender_address) + .unwrap_or_default(), + ); + + let mut sockets = self.api.sockets.lock().await; + let socket_context = sockets.get_mut(&socket_id)?; + + let subscription = if with_details { + Subscription::PendingTransactionsFull { address_filter } + } else { + Subscription::PendingTransactionsHash { address_filter } + }; + let subscription_id = socket_context.subscribe(rpc_request_id, subscription).await; + + // Only check pending. Regardless of block generation mode, ignore txs in latest block. + let pending_txs = self.get_pending_txs().await?; + for tx in pending_txs { + let notification = if with_details { + SubscriptionNotification::PendingTransaction(PendingTransactionNotification::Full( + Box::new(tx), + )) + } else { + SubscriptionNotification::PendingTransaction(PendingTransactionNotification::Hash( + TransactionHashWrapper { + hash: *tx.get_transaction_hash(), + sender_address: tx.get_sender_address(), + }, + )) + }; + socket_context.notify(subscription_id, notification).await; + } + + Ok(()) + } + + async fn subscribe_tx_status( + &self, + transaction_block_input: TransactionBlockInput, + rpc_request_id: Id, + socket_id: SocketId, + ) -> Result<(), ApiError> { + let TransactionBlockInput { transaction_hash, block_id } = transaction_block_input; + + let query_block_id = if let Some(block_id) = block_id { + block_id.0 + } else { + // if no block ID input, this eventually just subscribes the user to new blocks + BlockId::Tag(BlockTag::Latest) + }; + + let (query_block_number, latest_block_number) = + self.get_validated_block_number_range(query_block_id).await?; + + // perform the actual subscription + let mut sockets = self.api.sockets.lock().await; + let socket_context = sockets.get_mut(&socket_id)?; + + let subscription_tag = self.get_subscription_tag(query_block_id); + let subscription = + Subscription::TransactionStatus { tag: subscription_tag, transaction_hash }; + let subscription_id = socket_context.subscribe(rpc_request_id, subscription).await; + + let starknet = self.api.starknet.lock().await; + + if let Some(tx) = starknet.transactions.get(&transaction_hash) { + let notification = SubscriptionNotification::TransactionStatus(NewTransactionStatus { + transaction_hash, + status: tx.get_status(), + origin_tag: subscription_tag, + }); + match tx.get_block_number() { + Some(BlockNumber(block_number)) + if (query_block_number <= block_number + && block_number <= latest_block_number + && query_block_id != BlockId::Tag(BlockTag::Pending)) => + { + // if the number of the block when the tx was added is between + // specified/query block number and latest, notify the client + socket_context.notify(subscription_id, notification).await; + } + None if query_block_id == BlockId::Tag(BlockTag::Pending) => { + // if tx stored but no block number, it means it's pending, so only notify + // if the specified block ID is pending + socket_context.notify(subscription_id, notification).await; + } + _ => tracing::debug!("Tx status subscription: tx not reachable"), + } + } else { + tracing::debug!("Tx status subscription: tx not yet received") + } + + Ok(()) + } + + async fn subscribe_events( + &self, + maybe_subscription_input: Option, + rpc_request_id: Id, + socket_id: SocketId, + ) -> Result<(), ApiError> { + let address = maybe_subscription_input + .as_ref() + .and_then(|subscription_input| subscription_input.from_address); + + let starting_block_id = maybe_subscription_input + .as_ref() + .and_then(|subscription_input| subscription_input.block_id.as_ref()) + .map(|b| b.0) + .unwrap_or(BlockId::Tag(BlockTag::Latest)); + + disallow_pending_block(&starting_block_id)?; + self.get_validated_block_number_range(starting_block_id).await?; + + let keys_filter = + maybe_subscription_input.and_then(|subscription_input| subscription_input.keys); + + let mut sockets = self.api.sockets.lock().await; + let socket_context = sockets.get_mut(&socket_id)?; + let subscription = Subscription::Events { address, keys_filter: keys_filter.clone() }; + let subscription_id = socket_context.subscribe(rpc_request_id, subscription).await; + + let events = self.api.starknet.lock().await.get_unlimited_events( + Some(starting_block_id), + Some(BlockId::Tag(BlockTag::Latest)), + address, + keys_filter, + )?; + + for event in events { + socket_context.notify(subscription_id, SubscriptionNotification::Event(event)).await; + } + + Ok(()) + } +} diff --git a/crates/starknet-devnet-server/src/api/json_rpc/error.rs b/crates/starknet-devnet-server/src/api/json_rpc/error.rs index 112af8882..4959abfac 100644 --- a/crates/starknet-devnet-server/src/api/json_rpc/error.rs +++ b/crates/starknet-devnet-server/src/api/json_rpc/error.rs @@ -1,4 +1,4 @@ -use serde_json::json; +use starknet_core::stack_trace::{ErrorStack, Frame}; use starknet_types; use thiserror::Error; use tracing::error; @@ -27,7 +27,9 @@ pub enum ApiError { #[error("Class hash not found")] ClassHashNotFound, #[error("Contract error")] - ContractError { error: starknet_core::error::Error }, + ContractError { error_stack: ErrorStack }, + #[error("Transaction execution error")] + TransactionExecutionError { failure_index: usize, error_stack: ErrorStack }, #[error("There are no blocks")] NoBlocks, #[error("Requested page size is too big")] @@ -46,8 +48,8 @@ pub enum ApiError { UnsupportedAction { msg: String }, #[error("Invalid transaction nonce")] InvalidTransactionNonce, - #[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")] - InsufficientMaxFee, + #[error("The transaction's resources don't cover validation or the minimal transaction fee")] + InsufficientResourcesForValidate, #[error("Account balance is smaller than the transaction's max_fee")] InsufficientAccountBalance, #[error("Account validation failed")] @@ -60,8 +62,14 @@ pub enum ApiError { HttpApiError(#[from] HttpApiError), #[error("the compiled class hash did not match the one supplied in the transaction")] CompiledClassHashMismatch, - #[error("Transaction execution error")] - ExecutionError { execution_error: String, index: usize }, + #[error("Cannot go back more than 1024 blocks")] + TooManyBlocksBack, + #[error("This method does not support being called on the pending block")] + CallOnPending, + #[error("Invalid subscription id")] + InvalidSubscriptionId, + #[error("Devnet doesn't support storage proofs")] // slightly modified spec message + StorageProofNotSupported, } impl ApiError { @@ -94,14 +102,18 @@ impl ApiError { message: error_message.into(), data: None, }, - ApiError::ContractError { error: inner_error } => RpcError { + ApiError::ContractError { error_stack } => RpcError { code: crate::rpc_core::error::ErrorCode::ServerError(40), message: error_message.into(), - data: Some(json!( - { - "revert_error": anyhow::format_err!(inner_error).root_cause().to_string() - } - )), + data: Some(serialize_error_stack(&error_stack)), + }, + ApiError::TransactionExecutionError { error_stack, failure_index } => RpcError { + code: crate::rpc_core::error::ErrorCode::ServerError(41), + message: error_message.into(), + data: Some(serde_json::json!({ + "transaction_index": failure_index, + "execution_error": serialize_error_stack(&error_stack), + })), }, ApiError::NoBlocks => RpcError { code: crate::rpc_core::error::ErrorCode::ServerError(32), @@ -148,7 +160,7 @@ impl ApiError { message: msg.into(), data: None, }, - ApiError::InsufficientMaxFee => RpcError { + ApiError::InsufficientResourcesForValidate => RpcError { code: crate::rpc_core::error::ErrorCode::ServerError(53), message: error_message.into(), data: None, @@ -177,7 +189,7 @@ impl ApiError { starknet_core::error::Error::TransactionValidationError(validation_error), ) => { let api_err = match validation_error { - starknet_core::error::TransactionValidationError::InsufficientMaxFee => ApiError::InsufficientMaxFee, + starknet_core::error::TransactionValidationError::InsufficientResourcesForValidate => ApiError::InsufficientResourcesForValidate, starknet_core::error::TransactionValidationError::InvalidTransactionNonce => ApiError::InvalidTransactionNonce, starknet_core::error::TransactionValidationError::InsufficientAccountBalance => ApiError::InsufficientAccountBalance, starknet_core::error::TransactionValidationError::ValidationFailure { reason } => ApiError::ValidationFailure { reason }, @@ -200,15 +212,27 @@ impl ApiError { message: error_message.into(), data: None, }, - ApiError::ExecutionError { execution_error, index } => RpcError { - code: crate::rpc_core::error::ErrorCode::ServerError(41), + ApiError::HttpApiError(http_api_error) => http_api_error.http_api_error_to_rpc_error(), + ApiError::TooManyBlocksBack => RpcError { + code: crate::rpc_core::error::ErrorCode::ServerError(68), message: error_message.into(), - data: Some(json!({ - "transaction_index": index, - "execution_error": execution_error - })), + data: None, + }, + ApiError::CallOnPending => RpcError { + code: crate::rpc_core::error::ErrorCode::ServerError(69), + message: error_message.into(), + data: None, + }, + ApiError::InvalidSubscriptionId => RpcError { + code: crate::rpc_core::error::ErrorCode::ServerError(66), + message: error_message.into(), + data: None, + }, + ApiError::StorageProofNotSupported => RpcError { + code: crate::rpc_core::error::ErrorCode::ServerError(42), + message: error_message.into(), + data: None, }, - ApiError::HttpApiError(http_api_error) => http_api_error.http_api_error_to_rpc_error(), } } @@ -235,20 +259,52 @@ impl ApiError { | Self::OnlyLatestBlock | Self::UnsupportedAction { .. } | Self::InvalidTransactionNonce - | Self::InsufficientMaxFee | Self::InsufficientAccountBalance | Self::ValidationFailure { .. } | Self::HttpApiError(_) - | Self::CompiledClassHashMismatch - | Self::ExecutionError { .. } => false, + | Self::TransactionExecutionError { .. } + | Self::CallOnPending + | Self::TooManyBlocksBack + | Self::InvalidSubscriptionId + | Self::InsufficientResourcesForValidate + | Self::StorageProofNotSupported + | Self::CompiledClassHashMismatch => false, } } } +/// Constructs a recursive object from the provided `error_stack`. The topmost call (the first in +/// the stack's vector) is the outermost in the returned object. Vm frames are skipped as they don't +/// support nesting (no recursive properties). +fn serialize_error_stack(error_stack: &ErrorStack) -> serde_json::Value { + let mut recursive_error = serde_json::json!(null); + + for frame in error_stack.stack.iter().rev() { + match frame { + Frame::EntryPoint(entry_point_error_frame) => { + recursive_error = serde_json::json!({ + "contract_address": entry_point_error_frame.storage_address, + "class_hash": entry_point_error_frame.class_hash, + "selector": entry_point_error_frame.selector, + "error": recursive_error, + }); + } + Frame::Vm(_) => { /* do nothing */ } + Frame::StringFrame(msg) => { + recursive_error = serde_json::json!(*msg); + } + }; + } + + recursive_error +} + pub type StrictRpcResult = Result; #[cfg(test)] mod tests { + use starknet_core::stack_trace::ErrorStack; + use super::StrictRpcResult; use crate::api::json_rpc::error::ApiError; use crate::api::json_rpc::ToRpcResponseResult; @@ -320,27 +376,37 @@ mod tests { #[test] fn contract_error() { - fn test_error() -> starknet_core::error::Error { - starknet_core::error::Error::TransactionValidationError( - starknet_core::error::TransactionValidationError::ValidationFailure { - reason: "some reason".into(), - }, - ) - } - let error_expected_message = anyhow::format_err!(test_error()).root_cause().to_string(); + let api_error = + ApiError::ContractError { error_stack: ErrorStack::from_str_err("some_reason") }; - error_expected_code_and_message( - ApiError::ContractError { error: test_error() }, - 40, - "Contract error", - ); + error_expected_code_and_message(api_error, 40, "Contract error"); // check contract error data property - let error = ApiError::ContractError { error: test_error() }.api_error_to_rpc_error(); + let error = + ApiError::ContractError { error_stack: ErrorStack::from_str_err("some_reason") } + .api_error_to_rpc_error(); - assert_eq!( - error.data.unwrap().get("revert_error").unwrap().as_str().unwrap(), - &error_expected_message + assert_eq!(error.data.unwrap().as_str().unwrap(), "some_reason"); + } + + #[test] + fn transaction_execution_error() { + error_expected_code_and_message( + ApiError::TransactionExecutionError { + failure_index: 0, + error_stack: ErrorStack::from_str_err("anything"), + }, + 41, + "Transaction execution error", + ); + + error_expected_code_and_data( + ApiError::TransactionExecutionError { + failure_index: 1, + error_stack: ErrorStack::from_str_err("anything"), + }, + 41, + &serde_json::json!({ "transaction_index": 1, "execution_error": "anything" }), ); } @@ -366,17 +432,17 @@ mod tests { fn insufficient_max_fee_error() { let devnet_error = ApiError::StarknetDevnetError(starknet_core::error::Error::TransactionValidationError( - starknet_core::error::TransactionValidationError::InsufficientMaxFee, + starknet_core::error::TransactionValidationError::InsufficientResourcesForValidate, )); assert_eq!( devnet_error.api_error_to_rpc_error(), - ApiError::InsufficientMaxFee.api_error_to_rpc_error() + ApiError::InsufficientResourcesForValidate.api_error_to_rpc_error() ); error_expected_code_and_message( - ApiError::InsufficientMaxFee, + ApiError::InsufficientResourcesForValidate, 53, - "Max fee is smaller than the minimal transaction cost (validation plus fee transfer)", + "The transaction's resources don't cover validation or the minimal transaction fee", ); } @@ -421,7 +487,7 @@ mod tests { error_expected_code_and_data( ApiError::ValidationFailure { reason: reason.clone() }, 55, - &reason, + &serde_json::json!(reason), ); } @@ -436,12 +502,16 @@ mod tests { } } - fn error_expected_code_and_data(err: ApiError, expected_code: i64, expected_data: &str) { + fn error_expected_code_and_data( + err: ApiError, + expected_code: i64, + expected_data: &serde_json::Value, + ) { let error_result = StrictRpcResult::Err(err).to_rpc_result(); match error_result { crate::rpc_core::response::ResponseResult::Success(_) => panic!("Expected error"), crate::rpc_core::response::ResponseResult::Error(err) => { - assert_eq!(err.data.unwrap().as_str().unwrap(), expected_data); + assert_eq!(&err.data.unwrap(), expected_data); assert_eq!(err.code, crate::rpc_core::error::ErrorCode::ServerError(expected_code)) } } diff --git a/crates/starknet-devnet-server/src/api/json_rpc/mod.rs b/crates/starknet-devnet-server/src/api/json_rpc/mod.rs index ea7379ee7..1f3721cfe 100644 --- a/crates/starknet-devnet-server/src/api/json_rpc/mod.rs +++ b/crates/starknet-devnet-server/src/api/json_rpc/mod.rs @@ -1,4 +1,5 @@ mod endpoints; +mod endpoints_ws; pub mod error; pub mod models; pub(crate) mod origin_forwarder; @@ -8,17 +9,26 @@ mod write_endpoints; pub const RPC_SPEC_VERSION: &str = "0.7.1"; +use std::sync::Arc; + +use axum::extract::ws::{Message, WebSocket}; use enum_helper_macros::{AllVariantsSerdeRenames, VariantName}; +use futures::stream::SplitSink; +use futures::{SinkExt, StreamExt}; use models::{ BlockAndClassHashInput, BlockAndContractAddressInput, BlockAndIndexInput, CallInput, - EstimateFeeInput, EventsInput, GetStorageInput, TransactionHashInput, TransactionHashOutput, + EstimateFeeInput, EventsInput, EventsSubscriptionInput, GetStorageInput, GetStorageProofInput, + L1TransactionHashInput, PendingTransactionsSubscriptionInput, SubscriptionIdInput, + TransactionBlockInput, TransactionHashInput, TransactionHashOutput, }; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; use starknet_core::starknet::starknet_config::{DumpOn, StarknetConfig}; -use starknet_rs_core::types::{ContractClass as CodegenContractClass, Felt}; +use starknet_core::{CasmContractClass, StarknetBlock}; +use starknet_rs_core::types::{BlockId, BlockTag, ContractClass as CodegenContractClass, Felt}; use starknet_types::messaging::{MessageToL1, MessageToL2}; -use starknet_types::rpc::block::{Block, PendingBlock}; +use starknet_types::rpc::block::{Block, PendingBlock, ReorgData}; use starknet_types::rpc::estimate_message_fee::{ EstimateMessageFeeRequestWrapper, FeeEstimateWrapper, }; @@ -26,9 +36,11 @@ use starknet_types::rpc::gas_modification::{GasModification, GasModificationRequ use starknet_types::rpc::state::{PendingStateUpdate, StateUpdate}; use starknet_types::rpc::transaction_receipt::TransactionReceipt; use starknet_types::rpc::transactions::{ - BlockTransactionTrace, EventsChunk, SimulatedTransaction, TransactionTrace, TransactionWithHash, + BlockTransactionTrace, EventsChunk, L1HandlerTransactionStatus, SimulatedTransaction, + TransactionStatus, TransactionTrace, TransactionWithHash, }; use starknet_types::starknet_api::block::BlockNumber; +use tokio::sync::Mutex; use tracing::{error, info, trace}; use self::error::StrictRpcResult; @@ -36,7 +48,7 @@ use self::models::{ AccountAddressInput, BlockHashAndNumberOutput, BlockIdInput, BroadcastedDeclareTransactionInput, BroadcastedDeployAccountTransactionInput, BroadcastedInvokeTransactionInput, DeclareTransactionOutput, DeployAccountTransactionOutput, - SyncingOutput, TransactionStatusOutput, + SyncingOutput, }; use self::origin_forwarder::OriginForwarder; use super::http::endpoints::accounts::{BalanceQuery, PredeployedAccountsQuery}; @@ -56,10 +68,14 @@ use crate::api::json_rpc::models::{ use crate::api::serde_helpers::{empty_params, optional_params}; use crate::dump_util::dump_event; use crate::restrictive_mode::is_json_rpc_method_restricted; -use crate::rpc_core::error::RpcError; +use crate::rpc_core::error::{ErrorCode, RpcError}; use crate::rpc_core::request::RpcMethodCall; use crate::rpc_core::response::{ResponseResult, RpcResponse}; use crate::rpc_handler::RpcHandler; +use crate::subscribe::{ + NewTransactionStatus, PendingTransactionNotification, SocketId, SubscriptionNotification, + TransactionHashWrapper, +}; use crate::ServerConfig; /// Helper trait to easily convert results to rpc results @@ -117,6 +133,20 @@ impl RpcHandler for JsonRpcHandler { let is_request_forwardable = request.is_forwardable_to_origin(); // applicable if forking let is_request_dumpable = request.is_dumpable(); + // for later comparison and subscription notifications + let old_latest_block = if request.requires_notifying() { + Some(self.get_block_by_tag(BlockTag::Latest).await) + } else { + None + }; + + let old_pending_block = + if request.requires_notifying() && self.starknet_config.uses_pending_block() { + Some(self.get_block_by_tag(BlockTag::Pending).await) + } else { + None + }; + let starknet_resp = self.execute(request).await; // If locally we got an error and forking is set up, forward the request to the origin @@ -134,46 +164,61 @@ impl RpcHandler for JsonRpcHandler { } } + if let Err(e) = self.broadcast_changes(old_latest_block, old_pending_block).await { + return ResponseResult::Error(e.api_error_to_rpc_error()); + } + starknet_resp.to_rpc_result() } async fn on_call(&self, call: RpcMethodCall) -> RpcResponse { trace!(target: "rpc", id = ?call.id , method = ?call.method, "received method call"); - let RpcMethodCall { method, params, id, .. } = call.clone(); - let params: serde_json::Value = params.into(); - let deserializable_call = serde_json::json!({ - "method": &method, - "params": params - }); + if !self.allows_method(&call.method) { + return RpcResponse::from_rpc_error(RpcError::new(ErrorCode::MethodForbidden), call.id); + } - match serde_json::from_value::(deserializable_call) { + match to_json_rpc_request(&call) { Ok(req) => { - if let Some(restricted_methods) = &self.server_config.restricted_methods { - if is_json_rpc_method_restricted(&method, restricted_methods) { - return RpcResponse::new( - id, - RpcError::new(crate::rpc_core::error::ErrorCode::MethodForbidden), - ); - } - } - let result = self.on_request(req, call).await; - RpcResponse::new(id, result) + let result = self.on_request(req, call.clone()).await; + RpcResponse::new(call.id, result) } - Err(err) => { - let err = err.to_string(); - // since JSON-RPC specification requires returning a Method Not Found error, - // we apply a hacky way to induce this - checking the stringified error message - let distinctive_error = format!("unknown variant `{method}`"); - if err.contains(&distinctive_error) { - error!(target: "rpc", ?method, "failed to deserialize method due to unknown variant"); - RpcResponse::new(id, RpcError::method_not_found()) - } else { - error!(target: "rpc", ?method, ?err, "failed to deserialize method"); - RpcResponse::new(id, RpcError::invalid_params(err)) + Err(e) => RpcResponse::from_rpc_error(e, call.id), + } + } + + async fn on_websocket(&self, socket: WebSocket) { + let (socket_writer, mut socket_reader) = socket.split(); + let socket_writer = Arc::new(Mutex::new(socket_writer)); + + let socket_id = self.api.sockets.lock().await.insert(socket_writer.clone()); + + // listen to new messages coming through the socket + let mut socket_safely_closed = false; + while let Some(msg) = socket_reader.next().await { + match msg { + Ok(Message::Text(text)) => { + self.on_websocket_call(text.as_bytes(), socket_writer.clone(), socket_id).await; + } + Ok(Message::Binary(bytes)) => { + self.on_websocket_call(&bytes, socket_writer.clone(), socket_id).await; + } + Ok(Message::Close(_)) => { + socket_safely_closed = true; + break; + } + other => { + tracing::error!("Socket handler got an unexpected message: {other:?}") } } } + + if socket_safely_closed { + self.api.sockets.lock().await.remove(&socket_id); + tracing::info!("Websocket disconnected"); + } else { + tracing::error!("Failed socket read"); + } } } @@ -199,6 +244,179 @@ impl JsonRpcHandler { } } + /// The latest and pending block are always defined, so to avoid having to deal with Err/None in + /// places where this method is called, it is defined to return an empty accepted block, + /// even though that case should never happen. + async fn get_block_by_tag(&self, tag: BlockTag) -> StarknetBlock { + let starknet = self.api.starknet.lock().await; + match starknet.get_block(&BlockId::Tag(tag)) { + Ok(block) => block.clone(), + _ => StarknetBlock::create_empty_accepted(), + } + } + + async fn broadcast_pending_tx_changes( + &self, + old_pending_block: StarknetBlock, + ) -> Result<(), error::ApiError> { + let new_pending_block = self.get_block_by_tag(BlockTag::Pending).await; + let old_pending_txs = old_pending_block.get_transactions(); + let new_pending_txs = new_pending_block.get_transactions(); + + if new_pending_txs.len() > old_pending_txs.len() { + #[allow(clippy::expect_used)] + let new_tx_hash = new_pending_txs.last().expect("has at least one element"); + + let starknet = self.api.starknet.lock().await; + + let status = starknet + .get_transaction_execution_and_finality_status(*new_tx_hash) + .map_err(error::ApiError::StarknetDevnetError)?; + let tx_status_notification = + SubscriptionNotification::TransactionStatus(NewTransactionStatus { + transaction_hash: *new_tx_hash, + status, + origin_tag: BlockTag::Pending, + }); + + let tx = starknet + .get_transaction_by_hash(*new_tx_hash) + .map_err(error::ApiError::StarknetDevnetError)?; + let pending_tx_notification = SubscriptionNotification::PendingTransaction( + PendingTransactionNotification::Full(Box::new(tx.clone())), + ); + + let pending_tx_hash_notification = SubscriptionNotification::PendingTransaction( + PendingTransactionNotification::Hash(TransactionHashWrapper { + hash: *tx.get_transaction_hash(), + sender_address: tx.get_sender_address(), + }), + ); + + let notifications = + [tx_status_notification, pending_tx_notification, pending_tx_hash_notification]; + + self.api.sockets.lock().await.notify_subscribers(¬ifications).await; + } + + Ok(()) + } + + async fn broadcast_latest_changes( + &self, + new_latest_block: StarknetBlock, + ) -> Result<(), error::ApiError> { + let block_header = Box::new((&new_latest_block).into()); + let mut notifications = vec![SubscriptionNotification::NewHeads(block_header)]; + + let starknet = self.api.starknet.lock().await; + + for tx_hash in new_latest_block.get_transactions() { + let status = starknet + .get_transaction_execution_and_finality_status(*tx_hash) + .map_err(error::ApiError::StarknetDevnetError)?; + + notifications.push(SubscriptionNotification::TransactionStatus(NewTransactionStatus { + transaction_hash: *tx_hash, + status, + origin_tag: BlockTag::Latest, + })); + + // There are no pending txs in this mode, but basically we are pretending that the + // transaction existed for a short period of time in the pending block, thus triggering + // the notification. This is important for users depending on this subscription type to + // find out about all new transactions. + if !self.starknet_config.uses_pending_block() { + let tx = starknet + .get_transaction_by_hash(*tx_hash) + .map_err(error::ApiError::StarknetDevnetError)?; + notifications.push(SubscriptionNotification::PendingTransaction( + PendingTransactionNotification::Full(Box::new(tx.clone())), + )); + notifications.push(SubscriptionNotification::PendingTransaction( + PendingTransactionNotification::Hash(TransactionHashWrapper { + hash: *tx_hash, + sender_address: tx.get_sender_address(), + }), + )); + } + + let events = starknet.get_unlimited_events( + Some(BlockId::Tag(BlockTag::Latest)), + Some(BlockId::Tag(BlockTag::Latest)), + None, + None, + )?; + + for event in events { + notifications.push(SubscriptionNotification::Event(event)); + } + } + + self.api.sockets.lock().await.notify_subscribers(¬ifications).await; + Ok(()) + } + + /// Notify subscribers of what they are subscribed to. + async fn broadcast_changes( + &self, + old_latest_block: Option, + old_pending_block: Option, + ) -> Result<(), error::ApiError> { + let old_latest_block = if let Some(block) = old_latest_block { + block + } else { + return Ok(()); + }; + + if let Some(old_pending_block) = old_pending_block { + self.broadcast_pending_tx_changes(old_pending_block).await?; + } + + let new_latest_block = self.get_block_by_tag(BlockTag::Latest).await; + + match new_latest_block.block_number().cmp(&old_latest_block.block_number()) { + std::cmp::Ordering::Less => { + self.broadcast_reorg(old_latest_block, new_latest_block).await? + } + std::cmp::Ordering::Equal => { /* no changes required */ } + std::cmp::Ordering::Greater => self.broadcast_latest_changes(new_latest_block).await?, + } + + Ok(()) + } + + async fn broadcast_reorg( + &self, + old_latest_block: StarknetBlock, + new_latest_block: StarknetBlock, + ) -> Result<(), error::ApiError> { + // Since it is impossible to determine the hash of the former successor of new_latest_block + // directly, we iterate from old_latest_block all the way to the aborted successor of + // new_latest_block. + let new_latest_hash = new_latest_block.block_hash(); + let mut orphan_starting_block_hash = old_latest_block.block_hash(); + let starknet = self.api.starknet.lock().await; + loop { + let orphan_block = starknet.get_block(&BlockId::Hash(orphan_starting_block_hash))?; + let parent_hash = orphan_block.parent_hash(); + if parent_hash == new_latest_hash { + break; + } + orphan_starting_block_hash = parent_hash; + } + + let notification = SubscriptionNotification::Reorg(ReorgData { + starting_block_hash: orphan_starting_block_hash, + starting_block_number: new_latest_block.block_number().unchecked_next(), + ending_block_hash: old_latest_block.block_hash(), + ending_block_number: old_latest_block.block_number(), + }); + + self.api.sockets.lock().await.notify_subscribers(&[notification]).await; + Ok(()) + } + /// Matches the request to the corresponding enum variant and executes the request. async fn execute(&self, request: JsonRpcRequest) -> Result { trace!(target: "JsonRpcHandler::execute", "executing starknet request"); @@ -233,6 +451,10 @@ impl JsonRpcHandler { JsonRpcRequest::ClassByHash(BlockAndClassHashInput { block_id, class_hash }) => { self.get_class(block_id, class_hash).await } + JsonRpcRequest::CompiledCasmByClassHash(BlockAndClassHashInput { + block_id, + class_hash, + }) => self.get_compiled_casm(block_id, class_hash).await, JsonRpcRequest::ClassHashAtContractAddress(BlockAndContractAddressInput { block_id, contract_address, @@ -326,9 +548,57 @@ impl JsonRpcHandler { JsonRpcRequest::AccountBalance(data) => self.get_account_balance(data).await, JsonRpcRequest::Mint(data) => self.mint(data).await, JsonRpcRequest::DevnetConfig => self.get_devnet_config().await, + JsonRpcRequest::MessagesStatusByL1Hash(data) => self.get_messages_status(data).await, + JsonRpcRequest::StorageProof(data) => self.get_storage_proof(data).await, + } + } + + /// Takes `bytes` to be an encoded RPC call, executes it, and sends the response back via `ws`. + async fn on_websocket_call( + &self, + bytes: &[u8], + ws: Arc>>, + socket_id: SocketId, + ) { + let error_serialized = match serde_json::from_slice(bytes) { + Ok(rpc_call) => match self.on_websocket_rpc_call(&rpc_call, socket_id).await { + Ok(_) => return, + Err(e) => json!(RpcResponse::from_rpc_error(e, rpc_call.id)).to_string(), + }, + Err(e) => e.to_string(), + }; + + if let Err(e) = ws.lock().await.send(Message::Text(error_serialized)).await { + tracing::error!("Error sending websocket message: {e}"); } } + fn allows_method(&self, method: &String) -> bool { + if let Some(restricted_methods) = &self.server_config.restricted_methods { + if is_json_rpc_method_restricted(method, restricted_methods) { + return false; + } + } + + true + } + + /// Since some subscriptions might need to send multiple messages, sending messages other than + /// errors is left to individual RPC method handlers and this method returns an empty successful + /// Result. + async fn on_websocket_rpc_call( + &self, + call: &RpcMethodCall, + socket_id: SocketId, + ) -> Result<(), RpcError> { + trace!(target: "rpc", id = ?call.id , method = ?call.method, "received websocket call"); + + let req = to_json_rpc_request(call)?; + self.execute_ws(req, call.id.clone(), socket_id) + .await + .map_err(|e| e.api_error_to_rpc_error()) + } + async fn update_dump(&self, event: &RpcMethodCall) -> Result<(), RpcError> { match self.starknet_config.dump_on { Some(DumpOn::Block) => { @@ -378,6 +648,8 @@ pub enum JsonRpcRequest { StateUpdate(BlockIdInput), #[serde(rename = "starknet_getStorageAt")] StorageAt(GetStorageInput), + #[serde(rename = "starknet_getStorageProof")] + StorageProof(GetStorageProofInput), #[serde(rename = "starknet_getTransactionByHash")] TransactionByHash(TransactionHashInput), #[serde(rename = "starknet_getTransactionByBlockIdAndIndex")] @@ -386,8 +658,12 @@ pub enum JsonRpcRequest { TransactionReceiptByTransactionHash(TransactionHashInput), #[serde(rename = "starknet_getTransactionStatus")] TransactionStatusByHash(TransactionHashInput), + #[serde(rename = "starknet_getMessagesStatus")] + MessagesStatusByL1Hash(L1TransactionHashInput), #[serde(rename = "starknet_getClass")] ClassByHash(BlockAndClassHashInput), + #[serde(rename = "starknet_getCompiledCasm")] + CompiledCasmByClassHash(BlockAndClassHashInput), #[serde(rename = "starknet_getClassHashAt")] ClassHashAtContractAddress(BlockAndContractAddressInput), #[serde(rename = "starknet_getClassAt")] @@ -467,6 +743,64 @@ pub enum JsonRpcRequest { } impl JsonRpcRequest { + pub fn requires_notifying(&self) -> bool { + #![warn(clippy::wildcard_enum_match_arm)] + match self { + Self::AddDeclareTransaction(_) + | Self::AddDeployAccountTransaction(_) + | Self::AddInvokeTransaction(_) + | Self::PostmanFlush(_) + | Self::PostmanSendMessageToL2(_) + | Self::CreateBlock + | Self::AbortBlocks(_) + | Self::SetTime(_) + | Self::IncreaseTime(_) + | Self::Mint(_) => true, + Self::SpecVersion + | Self::BlockWithTransactionHashes(_) + | Self::BlockWithFullTransactions(_) + | Self::BlockWithReceipts(_) + | Self::StateUpdate(_) + | Self::StorageAt(_) + | Self::TransactionByHash(_) + | Self::TransactionByBlockAndIndex(_) + | Self::TransactionReceiptByTransactionHash(_) + | Self::TransactionStatusByHash(_) + | Self::MessagesStatusByL1Hash(_) + | Self::ClassByHash(_) + | Self::CompiledCasmByClassHash(_) + | Self::ClassHashAtContractAddress(_) + | Self::ClassAtContractAddress(_) + | Self::BlockTransactionCount(_) + | Self::Call(_) + | Self::EstimateFee(_) + | Self::BlockNumber + | Self::BlockHashAndNumber + | Self::ChainId + | Self::Syncing + | Self::Events(_) + | Self::ContractNonce(_) + | Self::EstimateMessageFee(_) + | Self::SimulateTransactions(_) + | Self::TraceTransaction(_) + | Self::BlockTransactionTraces(_) + | Self::ImpersonateAccount(_) + | Self::StopImpersonateAccount(_) + | Self::AutoImpersonate + | Self::StopAutoImpersonate + | Self::Dump(_) + | Self::Load(_) + | Self::PostmanLoadL1MessagingContract(_) + | Self::PostmanConsumeMessageFromL2(_) + | Self::SetGasPrice(_) + | Self::Restart(_) + | Self::PredeployedAccounts(_) + | Self::AccountBalance(_) + | Self::StorageProof(_) + | Self::DevnetConfig => false, + } + } + /// Should the request be retried by being forwarded to the forking origin? fn is_forwardable_to_origin(&self) -> bool { #[warn(clippy::wildcard_enum_match_arm)] @@ -493,6 +827,9 @@ impl JsonRpcRequest { | Self::EstimateMessageFee(_) | Self::SimulateTransactions(_) | Self::TraceTransaction(_) + | Self::MessagesStatusByL1Hash(_) + | Self::CompiledCasmByClassHash(_) + | Self::StorageProof(_) | Self::BlockTransactionTraces(_) => true, Self::SpecVersion | Self::ChainId @@ -575,11 +912,55 @@ impl JsonRpcRequest { | Self::Restart(_) | Self::PredeployedAccounts(_) | Self::AccountBalance(_) + | Self::MessagesStatusByL1Hash(_) + | Self::CompiledCasmByClassHash(_) + | Self::StorageProof(_) | Self::DevnetConfig => false, } } } +#[derive(Deserialize, AllVariantsSerdeRenames, VariantName)] +#[cfg_attr(test, derive(Debug))] +#[serde(tag = "method", content = "params")] +pub enum JsonRpcSubscriptionRequest { + #[serde(rename = "starknet_subscribeNewHeads", with = "optional_params")] + NewHeads(Option), + #[serde(rename = "starknet_subscribeTransactionStatus")] + TransactionStatus(TransactionBlockInput), + #[serde(rename = "starknet_subscribePendingTransactions", with = "optional_params")] + PendingTransactions(Option), + #[serde(rename = "starknet_subscribeEvents")] + Events(Option), + #[serde(rename = "starknet_unsubscribe")] + Unsubscribe(SubscriptionIdInput), +} + +fn to_json_rpc_request(call: &RpcMethodCall) -> Result +where + D: DeserializeOwned, +{ + let params: serde_json::Value = call.params.clone().into(); + let deserializable_call = json!({ + "method": call.method, + "params": params + }); + + serde_json::from_value::(deserializable_call).map_err(|err| { + let err = err.to_string(); + // since JSON-RPC specification requires returning a Method Not Found error, + // we apply a hacky way to induce this - checking the stringified error message + let distinctive_error = format!("unknown variant `{}`", call.method); + if err.contains(&distinctive_error) { + error!(target: "rpc", method = ?call.method, "failed to deserialize method due to unknown variant"); + RpcError::method_not_found() + } else { + error!(target: "rpc", method = ?call.method, ?err, "failed to deserialize method"); + RpcError::invalid_params(err) + } + }) +} + impl std::fmt::Display for JsonRpcRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.variant_name()) @@ -617,8 +998,9 @@ pub enum StarknetResponse { Felt(Felt), Transaction(TransactionWithHash), TransactionReceiptByTransactionHash(Box), - TransactionStatusByHash(TransactionStatusOutput), + TransactionStatusByHash(TransactionStatus), ContractClass(CodegenContractClass), + CompiledCasm(CasmContractClass), BlockTransactionCount(u64), Call(Vec), EstimateFee(Vec), @@ -634,6 +1016,8 @@ pub enum StarknetResponse { SimulateTransactions(Vec), TraceTransaction(TransactionTrace), BlockTransactionTraces(Vec), + MessagesStatusByL1Hash(Vec), + StorageProofs(serde_json::Value), // dummy, the corresponding RPC method always errors } #[derive(Serialize)] @@ -739,7 +1123,7 @@ mod requests_tests { // Errored json, hash is not prefixed with 0x assert_deserialization_fails( r#"{"method":"starknet_getTransactionByHash","params":{"transaction_hash":"134134"}}"#, - "Expected hex string to be prefixed by '0x'", + "expected hex string to be prefixed by '0x'", ); // TODO: ignored because of a Felt bug: https://github.com/starknet-io/types-rs/issues/81 // Errored json, hex is longer than 64 chars @@ -768,7 +1152,7 @@ mod requests_tests { assert_deserialization_fails( json_str.replace("0x", "").as_str(), - "Expected hex string to be prefixed by '0x'", + "expected hex string to be prefixed by '0x'", ); } @@ -779,7 +1163,7 @@ mod requests_tests { assert_deserialization_fails( json_str.replace("0x", "").as_str(), - "Expected hex string to be prefixed by '0x'", + "expected hex string to be prefixed by '0x'", ); } @@ -848,11 +1232,11 @@ mod requests_tests { r#""entry_point_selector":"134134""#, ) .as_str(), - "Expected hex string to be prefixed by '0x'", + "expected hex string to be prefixed by '0x'", ); assert_deserialization_fails( json_str.replace(r#""calldata":["0x134134"]"#, r#""calldata":["123"]"#).as_str(), - "Expected hex string to be prefixed by '0x'", + "expected hex string to be prefixed by '0x'", ); assert_deserialization_fails( json_str.replace(r#""calldata":["0x134134"]"#, r#""calldata":[123]"#).as_str(), @@ -1257,7 +1641,7 @@ mod requests_tests { let RpcMethodCall { method, params, .. } = serde_json::from_value(json_rpc_object).unwrap(); let params: serde_json::Value = params.into(); - let deserializable_call = serde_json::json!({ + let deserializable_call = json!({ "method": &method, "params": params }); diff --git a/crates/starknet-devnet-server/src/api/json_rpc/models.rs b/crates/starknet-devnet-server/src/api/json_rpc/models.rs index 01a809b31..fe7e75eb6 100644 --- a/crates/starknet-devnet-server/src/api/json_rpc/models.rs +++ b/crates/starknet-devnet-server/src/api/json_rpc/models.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; -use starknet_rs_core::types::{TransactionExecutionStatus, TransactionFinalityStatus}; +use starknet_rs_core::types::{ + Felt, Hash256, TransactionExecutionStatus, TransactionFinalityStatus, +}; use starknet_types::contract_address::ContractAddress; use starknet_types::felt::{BlockHash, ClassHash, TransactionHash}; use starknet_types::patricia_key::PatriciaKey; @@ -11,19 +13,19 @@ use starknet_types::rpc::transactions::{ }; use starknet_types::starknet_api::block::BlockNumber; -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct BlockIdInput { pub block_id: BlockId, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct TransactionHashInput { pub transaction_hash: TransactionHash, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] #[cfg_attr(test, derive(PartialEq, Eq))] pub struct GetStorageInput { @@ -32,34 +34,49 @@ pub struct GetStorageInput { pub block_id: BlockId, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] +pub struct ContractStorage { + pub contract_address: ContractAddress, + pub storage_keys: Vec, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct GetStorageProofInput { + pub block_id: BlockId, + pub class_hashes: Option>, + pub contract_addresses: Option>, + pub contract_storage_keys: Option, +} + +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct BlockAndIndexInput { pub block_id: BlockId, pub index: u64, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct BlockAndClassHashInput { pub block_id: BlockId, pub class_hash: ClassHash, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct BlockAndContractAddressInput { pub block_id: BlockId, pub contract_address: ContractAddress, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug)] #[serde(deny_unknown_fields)] pub struct AccountAddressInput { pub account_address: ContractAddress, } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize)] #[cfg_attr(test, derive(PartialEq, Eq))] #[serde(deny_unknown_fields)] pub struct CallInput { @@ -90,7 +107,7 @@ pub enum SyncingOutput { False(bool), } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize)] pub struct EventsInput { pub filter: EventFilter, } @@ -172,6 +189,42 @@ pub struct TransactionStatusOutput { pub execution_status: TransactionExecutionStatus, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct L1TransactionHashInput { + pub transaction_hash: Hash256, +} + +pub type SubscriptionId = u64; + +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct SubscriptionIdInput { + pub subscription_id: SubscriptionId, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct TransactionBlockInput { + pub transaction_hash: TransactionHash, + pub block_id: Option, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct PendingTransactionsSubscriptionInput { + pub transaction_details: Option, + pub sender_address: Option>, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +pub struct EventsSubscriptionInput { + pub block_id: Option, + pub from_address: Option, + pub keys: Option>>, +} + #[cfg(test)] mod tests { use starknet_rs_core::types::{BlockId as ImportedBlockId, BlockTag, Felt}; @@ -556,13 +609,12 @@ mod tests { ), ( r#"{"block_id": {"block_hash": 123}}"#, - // TODO: https://github.com/starknet-io/types-rs/issues/81#issuecomment-2230701335 - "Invalid block ID: invalid type: number, expected Failed to deserialize \ + "Invalid block ID: invalid type: number, expected a 32 byte array ([u8;32]) or a \ hexadecimal string", ), ( r#"{"block_id": {"block_hash": ""}}"#, - "Invalid block ID: Expected hex string to be prefixed by '0x", + "Invalid block ID: expected hex string to be prefixed by '0x", ), ] { match serde_json::from_str::(json_str) { diff --git a/crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs b/crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs index 49f3c7417..a344dd149 100644 --- a/crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs +++ b/crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs @@ -168,6 +168,8 @@ impl JsonRpcHandler { let restart_params = data.unwrap_or_default(); self.api.starknet.lock().await.restart(restart_params.restart_l1_to_l2_messaging)?; + self.api.sockets.lock().await.clear(); + Ok(super::JsonRpcResponse::Empty) } diff --git a/crates/starknet-devnet-server/src/api/mod.rs b/crates/starknet-devnet-server/src/api/mod.rs index 59d133dcc..103e1ffa0 100644 --- a/crates/starknet-devnet-server/src/api/mod.rs +++ b/crates/starknet-devnet-server/src/api/mod.rs @@ -8,6 +8,7 @@ use starknet_core::starknet::Starknet; use tokio::sync::Mutex; use crate::dump_util::DumpEvent; +use crate::subscribe::SocketCollection; /// Data that can be shared between threads with read write lock access /// Whatever needs to be accessed as information outside of Starknet could be added to this struct @@ -16,10 +17,15 @@ pub struct Api { // maybe the config should be added here next to the starknet instance pub starknet: Arc>, pub dumpable_events: Arc>>, + pub sockets: Arc>, } impl Api { pub fn new(starknet: Starknet) -> Self { - Self { starknet: Arc::new(Mutex::new(starknet)), dumpable_events: Default::default() } + Self { + starknet: Arc::new(Mutex::new(starknet)), + dumpable_events: Default::default(), + sockets: Arc::new(Mutex::new(SocketCollection::default())), + } } } diff --git a/crates/starknet-devnet-server/src/lib.rs b/crates/starknet-devnet-server/src/lib.rs index 1057e083d..6adce0160 100644 --- a/crates/starknet-devnet-server/src/lib.rs +++ b/crates/starknet-devnet-server/src/lib.rs @@ -7,6 +7,7 @@ pub mod rpc_core; /// handlers for axum server pub mod rpc_handler; pub mod server; +pub mod subscribe; #[cfg(any(test, feature = "test_utils"))] pub mod test_utils; diff --git a/crates/starknet-devnet-server/src/rpc_core/response.rs b/crates/starknet-devnet-server/src/rpc_core/response.rs index ce663538a..d3b2a3a4a 100644 --- a/crates/starknet-devnet-server/src/rpc_core/response.rs +++ b/crates/starknet-devnet-server/src/rpc_core/response.rs @@ -15,12 +15,6 @@ pub struct RpcResponse { pub(crate) result: ResponseResult, } -impl From for RpcResponse { - fn from(e: RpcError) -> Self { - Self { jsonrpc: Version::V2, id: None, result: ResponseResult::Error(e) } - } -} - impl RpcResponse { pub fn new(id: Id, content: impl Into) -> Self { RpcResponse { jsonrpc: Version::V2, id: Some(id), result: content.into() } @@ -29,6 +23,10 @@ impl RpcResponse { pub fn invalid_request(id: Id) -> Self { Self::new(id, RpcError::invalid_request()) } + + pub fn from_rpc_error(e: RpcError, id: Id) -> Self { + Self { jsonrpc: Version::V2, id: Some(id), result: ResponseResult::Error(e) } + } } /// Represents the result of a call either success or error diff --git a/crates/starknet-devnet-server/src/rpc_handler.rs b/crates/starknet-devnet-server/src/rpc_handler.rs index 536b7b2bc..2117b8c59 100644 --- a/crates/starknet-devnet-server/src/rpc_handler.rs +++ b/crates/starknet-devnet-server/src/rpc_handler.rs @@ -1,7 +1,9 @@ use std::fmt::{self}; use axum::extract::rejection::JsonRejection; -use axum::extract::State; +use axum::extract::ws::WebSocket; +use axum::extract::{State, WebSocketUpgrade}; +use axum::response::IntoResponse; use axum::Json; use futures::{future, FutureExt}; use serde::de::DeserializeOwned; @@ -33,6 +35,9 @@ pub trait RpcHandler: Clone + Send + Sync + 'static { /// **Note**: override this function if the expected `Request` deviates from `{ "method" : /// "", "params": "" }` async fn on_call(&self, call: RpcMethodCall) -> RpcResponse; + + /// Handles websocket connection, from start to finish. + async fn on_websocket(&self, mut socket: WebSocket); } /// Handles incoming JSON-RPC Request @@ -52,6 +57,18 @@ pub async fn handle( } } +pub async fn handle_socket( + ws_upgrade: WebSocketUpgrade, + State(handler): State, +) -> impl IntoResponse { + tracing::info!("New websocket connection!"); + ws_upgrade.on_failed_upgrade(|e| tracing::error!("Failed websocket upgrade: {e:?}")).on_upgrade( + move |socket| async move { + handler.on_websocket(socket).await; + }, + ) +} + #[macro_export] /// Match a list of comma-separated pairs enclosed in square brackets. First pair member is the HTTP /// path which is mapped to an RPC request with the method that is the second pair member. Using the diff --git a/crates/starknet-devnet-server/src/server.rs b/crates/starknet-devnet-server/src/server.rs index c51457966..6ae753c5c 100644 --- a/crates/starknet-devnet-server/src/server.rs +++ b/crates/starknet-devnet-server/src/server.rs @@ -55,6 +55,7 @@ fn json_rpc_routes(json_rpc_handler: TJsonRpcHandle Router::new() .route("/", post(rpc_handler::handle::)) .route("/rpc", post(rpc_handler::handle::)) + .route("/ws", get(rpc_handler::handle_socket::)) .with_state(json_rpc_handler) } diff --git a/crates/starknet-devnet-server/src/subscribe.rs b/crates/starknet-devnet-server/src/subscribe.rs new file mode 100644 index 000000000..c943bdc16 --- /dev/null +++ b/crates/starknet-devnet-server/src/subscribe.rs @@ -0,0 +1,301 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use axum::extract::ws::{Message, WebSocket}; +use futures::stream::SplitSink; +use futures::SinkExt; +use serde::{self, Serialize}; +use starknet_core::starknet::events::check_if_filter_applies_for_event; +use starknet_rs_core::types::{BlockTag, Felt}; +use starknet_types::contract_address::ContractAddress; +use starknet_types::emitted_event::EmittedEvent; +use starknet_types::felt::TransactionHash; +use starknet_types::rpc::block::{BlockHeader, ReorgData}; +use starknet_types::rpc::transactions::{TransactionStatus, TransactionWithHash}; +use tokio::sync::Mutex; + +use crate::api::json_rpc::error::ApiError; +use crate::api::json_rpc::models::SubscriptionId; +use crate::rpc_core::request::Id; + +pub type SocketId = u64; + +#[derive(Default)] +pub struct SocketCollection { + sockets: HashMap, +} + +impl SocketCollection { + pub fn get_mut(&mut self, socket_id: &SocketId) -> Result<&mut SocketContext, ApiError> { + self.sockets.get_mut(socket_id).ok_or(ApiError::StarknetDevnetError( + starknet_core::error::Error::UnexpectedInternalError { + msg: format!("Unregistered socket ID: {socket_id}"), + }, + )) + } + + /// Assigns a random socket ID to the socket whose `socket_writer` is provided. Returns the ID. + pub fn insert(&mut self, socket_writer: Arc>>) -> SocketId { + let socket_id = rand::random(); + self.sockets.insert(socket_id, SocketContext::from_sender(socket_writer)); + socket_id + } + + pub fn remove(&mut self, socket_id: &SocketId) { + self.sockets.remove(socket_id); + } + + pub async fn notify_subscribers(&self, notifications: &[SubscriptionNotification]) { + for (_, socket_context) in self.sockets.iter() { + for notification in notifications { + socket_context.notify_subscribers(notification).await; + } + } + } + + pub fn clear(&mut self) { + self.sockets.clear(); + tracing::info!("Websocket memory cleared. No subscribers."); + } +} + +#[derive(Debug)] +pub struct AddressFilter { + address_container: Vec, +} + +impl AddressFilter { + pub(crate) fn new(address_container: Vec) -> Self { + Self { address_container } + } + pub(crate) fn passes(&self, address: &ContractAddress) -> bool { + self.address_container.is_empty() || self.address_container.contains(address) + } +} + +#[derive(Debug)] +pub enum Subscription { + NewHeads, + TransactionStatus { tag: BlockTag, transaction_hash: TransactionHash }, + PendingTransactionsFull { address_filter: AddressFilter }, + PendingTransactionsHash { address_filter: AddressFilter }, + Events { address: Option, keys_filter: Option>> }, +} + +impl Subscription { + fn confirm(&self, id: SubscriptionId) -> SubscriptionConfirmation { + match self { + Subscription::NewHeads => SubscriptionConfirmation::NewSubscription(id), + Subscription::TransactionStatus { .. } => SubscriptionConfirmation::NewSubscription(id), + Subscription::PendingTransactionsFull { .. } + | Subscription::PendingTransactionsHash { .. } => { + SubscriptionConfirmation::NewSubscription(id) + } + Subscription::Events { .. } => SubscriptionConfirmation::NewSubscription(id), + } + } + + pub fn matches(&self, notification: &SubscriptionNotification) -> bool { + match (self, notification) { + (Subscription::NewHeads, SubscriptionNotification::NewHeads(_)) => true, + ( + Subscription::TransactionStatus { tag, transaction_hash: subscription_hash }, + SubscriptionNotification::TransactionStatus(notification), + ) => { + tag == ¬ification.origin_tag + && subscription_hash == ¬ification.transaction_hash + } + ( + Subscription::PendingTransactionsFull { address_filter }, + SubscriptionNotification::PendingTransaction(PendingTransactionNotification::Full( + tx, + )), + ) => match tx.get_sender_address() { + Some(address) => address_filter.passes(&address), + None => true, + }, + ( + Subscription::PendingTransactionsHash { address_filter }, + SubscriptionNotification::PendingTransaction(PendingTransactionNotification::Hash( + hash_wrapper, + )), + ) => match hash_wrapper.sender_address { + Some(address) => address_filter.passes(&address), + None => true, + }, + ( + Subscription::Events { address, keys_filter }, + SubscriptionNotification::Event(event), + ) => check_if_filter_applies_for_event(address, keys_filter, &event.into()), + ( + Subscription::NewHeads + | Subscription::TransactionStatus { .. } + | Subscription::Events { .. }, + SubscriptionNotification::Reorg(_), + ) => true, // any subscription other than pending tx requires reorg notification + _ => false, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +enum SubscriptionConfirmation { + NewSubscription(SubscriptionId), + Unsubscription(bool), +} + +#[derive(Debug, Clone, Serialize)] +pub struct NewTransactionStatus { + pub transaction_hash: TransactionHash, + pub status: TransactionStatus, + /// which block this notification originates from: pending or latest + #[serde(skip)] + pub origin_tag: BlockTag, +} + +#[derive(Debug, Clone)] +pub struct TransactionHashWrapper { + pub hash: TransactionHash, + pub sender_address: Option, +} + +impl Serialize for TransactionHashWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.hash.serialize(serializer) + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum PendingTransactionNotification { + Hash(TransactionHashWrapper), + Full(Box), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum SubscriptionNotification { + NewHeads(Box), + TransactionStatus(NewTransactionStatus), + PendingTransaction(PendingTransactionNotification), + Event(EmittedEvent), + Reorg(ReorgData), +} + +impl SubscriptionNotification { + fn method_name(&self) -> &'static str { + match self { + SubscriptionNotification::NewHeads(_) => "starknet_subscriptionNewHeads", + SubscriptionNotification::TransactionStatus(_) => { + "starknet_subscriptionTransactionStatus" + } + SubscriptionNotification::PendingTransaction(_) => { + "starknet_subscriptionPendingTransactions" + } + SubscriptionNotification::Event(_) => "starknet_subscriptionEvents", + SubscriptionNotification::Reorg(_) => "starknet_subscriptionReorg", + } + } +} + +#[derive(Debug)] +enum SubscriptionResponse { + Confirmation { rpc_request_id: Id, result: SubscriptionConfirmation }, + Notification { subscription_id: SubscriptionId, data: Box }, +} + +impl SubscriptionResponse { + fn to_serialized_rpc_response(&self) -> serde_json::Value { + let mut resp = match self { + SubscriptionResponse::Confirmation { rpc_request_id, result } => { + serde_json::json!({ + "id": rpc_request_id, + "result": result, + }) + } + SubscriptionResponse::Notification { subscription_id, data } => { + serde_json::json!({ + "method": data.method_name(), + "params": { + "subscription_id": subscription_id, + "result": data, + } + }) + } + }; + + resp["jsonrpc"] = "2.0".into(); + resp + } +} + +pub struct SocketContext { + /// The sender part of the socket's own channel + sender: Arc>>, + subscriptions: HashMap, +} + +impl SocketContext { + pub fn from_sender(sender: Arc>>) -> Self { + Self { sender, subscriptions: HashMap::new() } + } + + async fn send(&self, subscription_response: SubscriptionResponse) { + let resp_serialized = subscription_response.to_serialized_rpc_response().to_string(); + + if let Err(e) = self.sender.lock().await.send(Message::Text(resp_serialized)).await { + tracing::error!("Failed writing to socket: {}", e.to_string()); + } + } + + pub async fn subscribe( + &mut self, + rpc_request_id: Id, + subscription: Subscription, + ) -> SubscriptionId { + let subscription_id = rand::random(); + + let confirmation = subscription.confirm(subscription_id); + self.subscriptions.insert(subscription_id, subscription); + + self.send(SubscriptionResponse::Confirmation { rpc_request_id, result: confirmation }) + .await; + + subscription_id + } + + pub async fn unsubscribe( + &mut self, + rpc_request_id: Id, + subscription_id: SubscriptionId, + ) -> Result<(), ApiError> { + match self.subscriptions.remove(&subscription_id) { + Some(_) => { + self.send(SubscriptionResponse::Confirmation { + rpc_request_id, + result: SubscriptionConfirmation::Unsubscription(true), + }) + .await; + Ok(()) + } + None => Err(ApiError::InvalidSubscriptionId), + } + } + + pub async fn notify(&self, subscription_id: SubscriptionId, data: SubscriptionNotification) { + self.send(SubscriptionResponse::Notification { subscription_id, data: Box::new(data) }) + .await; + } + + pub async fn notify_subscribers(&self, notification: &SubscriptionNotification) { + for (subscription_id, subscription) in self.subscriptions.iter() { + if subscription.matches(notification) { + self.notify(*subscription_id, notification.clone()).await; + } + } + } +} diff --git a/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_api_openrpc.json b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_api_openrpc.json new file mode 100644 index 000000000..0bd0243d4 --- /dev/null +++ b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_api_openrpc.json @@ -0,0 +1,4124 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Node API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_specVersion", + "summary": "Returns the version of the Starknet JSON-RPC specification being used", + "params": [], + "result": { + "name": "result", + "description": "Semver of Starknet's JSON-RPC spec being used", + "required": true, + "schema": { + "title": "JSON-RPC spec version", + "type": "string" + } + } + }, + { + "name": "starknet_getBlockWithTxHashes", + "summary": "Get block information with transaction hashes given the block id", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with transaction hashes", + "schema": { + "title": "Starknet get block hash with tx hashes result", + "oneOf": [ + { + "title": "Block with transaction hashes", + "$ref": "#/components/schemas/BLOCK_WITH_TX_HASHES" + }, + { + "title": "Pending block with transaction hashes", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_TX_HASHES" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockWithTxs", + "summary": "Get block information with full transactions given the block id", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with full transactions", + "schema": { + "title": "Starknet get block with txs result", + "oneOf": [ + { + "title": "Block with transactions", + "$ref": "#/components/schemas/BLOCK_WITH_TXS" + }, + { + "title": "Pending block with transactions", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_TXS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockWithReceipts", + "summary": "Get block information with full transactions and receipts given the block id", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The resulting block information with full transactions", + "schema": { + "title": "Starknet get block with txs and receipts result", + "oneOf": [ + { + "title": "Block with transactions", + "$ref": "#/components/schemas/BLOCK_WITH_RECEIPTS" + }, + { + "title": "Pending block with transactions", + "$ref": "#/components/schemas/PENDING_BLOCK_WITH_RECEIPTS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStateUpdate", + "summary": "Get the information about the result of executing the requested block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The information about the state update of the requested block", + "schema": { + "title": "Starknet get state update result", + "oneOf": [ + { + "title": "State update", + "$ref": "#/components/schemas/STATE_UPDATE" + }, + { + "title": "Pending state update", + "$ref": "#/components/schemas/PENDING_STATE_UPDATE" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStorageAt", + "summary": "Get the value of the storage at the given address and key", + "params": [ + { + "name": "contract_address", + "description": "The address of the contract to read from", + "summary": "The address of the contract to read from", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "key", + "description": "The key to the storage value for the given contract", + "summary": "The key to the storage value for the given contract", + "required": true, + "schema": { + "title": "Storage key", + "$ref": "#/components/schemas/STORAGE_KEY" + } + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The value at the given key for the given contract. 0 if no value is found", + "summary": "The value at the given key for the given contract.", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionStatus", + "summary": "Gets the transaction status (possibly reflecting that the tx is still in the mempool, or dropped from it)", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "$ref": "#/components/schemas/TXN_STATUS_RESULT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getMessagesStatus", + "summary": "Given an l1 tx hash, returns the associated l1_handler tx hashes and statuses for all L1 -> L2 messages sent by the l1 ransaction, ordered by the l1 tx sending order", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the L1 transaction that sent L1->L2 messages", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/L1_TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "type": "array", + "items": { + "type": "object", + "title": "status", + "properties": { + "transaction_hash": { + "$ref": "#/components/schemas/TXN_HASH" + }, + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" + }, + "failure_reason": { + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED", + "type": "string" + } + }, + "required": [ + "transaction_hash", + "finality_status" + ] + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByHash", + "summary": "Get the details and status of a submitted transaction", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getTransactionByBlockIdAndIndex", + "summary": "Get the details of a transaction by a given block id and index", + "description": "Get the details of the transaction given by the identified block and index in that block. If no transaction is found, null is returned.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "index", + "summary": "The index in the block to search for the transaction", + "required": true, + "schema": { + "title": "Index", + "type": "integer", + "minimum": 0 + } + } + ], + "result": { + "name": "transactionResult", + "schema": { + "title": "Transaction", + "allOf": [ + { + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/INVALID_TXN_INDEX" + } + ] + }, + { + "name": "starknet_getTransactionReceipt", + "summary": "Get the transaction receipt by the transaction hash", + "paramStructure": "by-name", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the requested transaction", + "required": true, + "schema": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "result", + "schema": { + "title": "Transaction receipt with block info", + "$ref": "#/components/schemas/TXN_RECEIPT_WITH_BLOCK_INFO" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClass", + "summary": "Get the contract class definition in the given block associated with the given hash", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "class_hash", + "description": "The hash of the requested contract class", + "required": true, + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + } + ], + "result": { + "name": "result", + "description": "The contract class, if found", + "schema": { + "title": "Starknet get class result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassHashAt", + "summary": "Get the contract class hash in the given block for the contract deployed at the given address", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose class hash will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The class hash of the given contract", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getClassAt", + "summary": "Get the contract class definition in the given block at the given address", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose class definition will be returned", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract class", + "schema": { + "title": "Starknet get class at result", + "oneOf": [ + { + "title": "Deprecated contract class", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + }, + { + "title": "Contract class", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getBlockTransactionCount", + "summary": "Get the number of transactions in a block given a block id", + "description": "Returns the number of transactions in the designated block.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "The number of transactions in the designated block", + "summary": "The number of transactions in the designated block", + "schema": { + "title": "Block transaction count", + "type": "integer", + "minimum": 0 + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_call", + "summary": "call a starknet function without creating a StarkNet transaction", + "description": "Calls a function in a contract and returns the return value. Using this call will not create a transaction; hence, will not change the state", + "params": [ + { + "name": "request", + "summary": "The details of the function call", + "schema": { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "summary": "The function's return value", + "description": "The function's return value, as defined in the Cairo output", + "schema": { + "type": "array", + "title": "Field element", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateFee", + "summary": "estimate the fee for of StarkNet transactions", + "description": "Estimates the resources required by a given sequence of transactions when applied on a given state. If one of the transactions reverts or fails due to any reason (e.g. validation failure or an internal error), a TRANSACTION_EXECUTION_ERROR is returned. For v0-2 transactions the estimate is given in wei, and for v3 transactions it is given in fri.", + "params": [ + { + "name": "request", + "summary": "The transaction to estimate", + "schema": { + "type": "array", + "description": "a sequence of transactions to estimate, running each transaction on the state resulting from applying all the previous ones", + "title": "Transaction", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + }, + "required": true + }, + { + "name": "simulation_flags", + "description": "describes what parts of the transaction should be executed", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SIMULATION_FLAG_FOR_ESTIMATE_FEE" + } + } + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimations", + "schema": { + "title": "Estimation", + "type": "array", + "description": "a sequence of fee estimatione where the i'th estimate corresponds to the i'th transaction", + "items": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/TRANSACTION_EXECUTION_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_estimateMessageFee", + "summary": "estimate the L2 fee of a message sent on L1", + "description": "estimates the resources required by the l1_handler transaction induced by the message", + "params": [ + { + "name": "message", + "description": "the message's parameters", + "schema": { + "$ref": "#/components/schemas/MSG_FROM_L1" + }, + "required": true + }, + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "result", + "description": "the fee estimation", + "schema": { + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + }, + "errors": [ + { + "$ref": "#/components/errors/CONTRACT_ERROR" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + }, + { + "name": "starknet_blockNumber", + "summary": "Get the most recent accepted block number", + "params": [], + "result": { + "name": "result", + "description": "The latest block number", + "schema": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_blockHashAndNumber", + "summary": "Get the most recent accepted block hash and number", + "params": [], + "result": { + "name": "result", + "description": "The latest block hash and number", + "schema": { + "title": "Starknet block hash and number result", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "block_hash", + "block_number" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/NO_BLOCKS" + } + ] + }, + { + "name": "starknet_chainId", + "summary": "Return the currently configured StarkNet chain id", + "params": [], + "result": { + "name": "result", + "description": "The chain id this node is connected to", + "schema": { + "title": "Chain id", + "$ref": "#/components/schemas/CHAIN_ID" + } + } + }, + { + "name": "starknet_syncing", + "summary": "Returns an object about the sync status, or false if the node is not synching", + "params": [], + "result": { + "name": "syncing", + "summary": "The state of the synchronization, or false if the node is not synchronizing", + "description": "The status of the node, if it is currently synchronizing state. FALSE otherwise", + "schema": { + "title": "SyncingStatus", + "oneOf": [ + { + "type": "boolean", + "title": "False", + "description": "only legal value is FALSE here" + }, + { + "title": "Sync status", + "$ref": "#/components/schemas/SYNC_STATUS" + } + ] + } + } + }, + { + "name": "starknet_getEvents", + "summary": "Returns all events matching the given filter", + "description": "Returns all event objects matching the conditions in the provided filter", + "params": [ + { + "name": "filter", + "summary": "The conditions used to filter the returned events", + "required": true, + "schema": { + "title": "Events request", + "allOf": [ + { + "title": "Event filter", + "$ref": "#/components/schemas/EVENT_FILTER" + }, + { + "title": "Result page request", + "$ref": "#/components/schemas/RESULT_PAGE_REQUEST" + } + ] + } + } + ], + "result": { + "name": "events", + "description": "All the event objects matching the filter", + "schema": { + "title": "Events chunk", + "$ref": "#/components/schemas/EVENTS_CHUNK" + } + }, + "errors": [ + { + "$ref": "#/components/errors/PAGE_SIZE_TOO_BIG" + }, + { + "$ref": "#/components/errors/INVALID_CONTINUATION_TOKEN" + }, + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/TOO_MANY_KEYS_IN_FILTER" + } + ] + }, + { + "name": "starknet_getNonce", + "summary": "Get the nonce associated with the given address in the given block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "title": "Block id", + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "contract_address", + "description": "The address of the contract whose nonce we're seeking", + "required": true, + "schema": { + "title": "Address", + "$ref": "#/components/schemas/ADDRESS" + } + } + ], + "result": { + "name": "result", + "description": "The contract's nonce at the requested state", + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/CONTRACT_NOT_FOUND" + } + ] + }, + { + "name": "starknet_getStorageProof", + "summary": "get merkle paths in one of the state tries: global state, classes, individual contract", + "params": [ + { + "name": "class_hashes", + "description": "a list of the class hashes for which we want to prove membership in the classes trie", + "required": false, + "schema": { + "title": "classes", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + { + "name": "contract_addresses", + "description": "a list of contracts for which we want to prove membership in the global state trie", + "required": false, + "schema": { + "title": "contracts", + "type": "array", + "items": { + "$ref": "#/components/schemas/ADDRESS" + } + } + }, + { + "name": "contracts_storage_keys", + "description": "a list of (contract_address, storage_keys) pairs", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contract_address": { + "$ref": "#/components/schemas/ADDRESS" + }, + "storage_keys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + } + } + } + } + ], + "result": { + "name": "result", + "description": "The contract's nonce at the requested state", + "schema": { + "type": "object", + "properties": { + "classes_proof": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + }, + "contracts_proof": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + }, + "contracts_storage_proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NODE_HASH_TO_NODE_MAPPING" + } + } + } + } + } + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "EVENTS_CHUNK": { + "title": "Events chunk", + "type": "object", + "properties": { + "events": { + "type": "array", + "title": "Matching Events", + "items": { + "$ref": "#/components/schemas/EMITTED_EVENT" + } + }, + "continuation_token": { + "title": "Continuation token", + "description": "Use this token in a subsequent query to obtain the next page. Should not appear if there are no more pages.", + "type": "string" + } + }, + "required": [ + "events" + ] + }, + "RESULT_PAGE_REQUEST": { + "title": "Result page request", + "type": "object", + "properties": { + "continuation_token": { + "title": "Continuation token", + "description": "The token returned from the previous query. If no token is provided the first page is returned.", + "type": "string" + }, + "chunk_size": { + "title": "Chunk size", + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "chunk_size" + ] + }, + "EMITTED_EVENT": { + "title": "Emitted event", + "description": "Event information decorated with metadata on where it was emitted / An event emitted as a result of transaction execution", + "allOf": [ + { + "title": "Event", + "description": "The event information", + "$ref": "#/components/schemas/EVENT" + }, + { + "title": "Event context", + "description": "The event emission information", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "description": "The hash of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The number of the block in which the event was emitted", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "transaction_hash": { + "title": "Transaction hash", + "description": "The transaction that emitted the event", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + }, + "EVENT": { + "title": "Event", + "description": "A StarkNet event", + "allOf": [ + { + "title": "Event emitter", + "type": "object", + "properties": { + "from_address": { + "title": "From address", + "$ref": "#/components/schemas/ADDRESS" + } + }, + "required": [ + "from_address" + ] + }, + { + "title": "Event content", + "$ref": "#/components/schemas/EVENT_CONTENT" + } + ] + }, + "EVENT_CONTENT": { + "title": "Event content", + "description": "The content of an event", + "type": "object", + "properties": { + "keys": { + "type": "array", + "title": "Keys", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "data": { + "type": "array", + "title": "Data", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "keys", + "data" + ] + }, + "EVENT_KEYS": { + "title": "Keys", + "description": "The keys to filter over", + "type": "array", + "items": { + "title": "Keys", + "description": "Per key (by position), designate the possible values to be matched for events to be returned. Empty array designates 'any' value", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "EVENT_FILTER": { + "title": "Event filter", + "description": "An event filter/query", + "type": "object", + "properties": { + "from_block": { + "title": "from block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "to_block": { + "title": "to block", + "$ref": "#/components/schemas/BLOCK_ID" + }, + "address": { + "title": "from contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "keys": { + "title": "event keys", + "description": "The keys to filter over", + "$ref": "#/components/schemas/EVENT_KEYS" + } + }, + "required": [] + }, + "BLOCK_ID": { + "title": "Block id", + "description": "Block hash, number or tag", + "oneOf": [ + { + "title": "Block hash", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + } + }, + "required": [ + "block_hash" + ] + }, + { + "title": "Block number", + "type": "object", + "properties": { + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "block_number" + ] + }, + { + "title": "Block tag", + "$ref": "#/components/schemas/BLOCK_TAG" + } + ] + }, + "BLOCK_TAG": { + "title": "Block tag", + "type": "string", + "description": "A tag specifying a dynamic reference to a block", + "enum": [ + "latest", + "pending" + ] + }, + "SYNC_STATUS": { + "title": "Sync status", + "type": "object", + "description": "An object describing the node synchronization status", + "properties": { + "starting_block_hash": { + "title": "Starting block hash", + "description": "The hash of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "starting_block_num": { + "title": "Starting block number", + "description": "The number (height) of the block from which the sync started", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "current_block_hash": { + "title": "Current block hash", + "description": "The hash of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "current_block_num": { + "title": "Current block number", + "description": "The number (height) of the current block being synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "highest_block_hash": { + "title": "Highest block hash", + "description": "The hash of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "highest_block_num": { + "title": "Highest block number", + "description": "The number (height) of the estimated highest block to be synchronized", + "$ref": "#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "starting_block_hash", + "starting_block_num", + "current_block_hash", + "current_block_num", + "highest_block_hash", + "highest_block_num" + ] + }, + "NUM_AS_HEX": { + "title": "Number as hex", + "description": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "u64": { + "type": "string", + "title": "u64", + "description": "64 bit integers, represented by hex string of length at most 16", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,15})$" + }, + "u128": { + "type": "string", + "title": "u128", + "description": "64 bit integers, represented by hex string of length at most 32", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,31})$" + }, + "CHAIN_ID": { + "title": "Chain id", + "description": "StarkNet chain id, given in hex representation.", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "STATE_DIFF": { + "description": "The change in state applied in this block, given as a mapping of addresses to the new values and/or new contracts", + "type": "object", + "properties": { + "storage_diffs": { + "title": "Storage diffs", + "type": "array", + "items": { + "description": "The changes in the storage per contract address", + "$ref": "#/components/schemas/CONTRACT_STORAGE_DIFF_ITEM" + } + }, + "deprecated_declared_classes": { + "title": "Deprecated declared classes", + "type": "array", + "items": { + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "declared_classes": { + "title": "Declared classes", + "type": "array", + "items": { + "title": "New classes", + "type": "object", + "description": "The declared class hash and compiled class hash", + "properties": { + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The Cairo assembly hash corresponding to the declared class", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "deployed_contracts": { + "title": "Deployed contracts", + "type": "array", + "items": { + "description": "A new contract deployed as part of the state update", + "$ref": "#/components/schemas/DEPLOYED_CONTRACT_ITEM" + } + }, + "replaced_classes": { + "title": "Replaced classes", + "type": "array", + "items": { + "description": "The list of contracts whose class was replaced", + "title": "Replaced class", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract whose class was replaced", + "$ref": "#/components/schemas/ADDRESS" + }, + "class_hash": { + "title": "Class hash", + "description": "The new class hash", + "$ref": "#/components/schemas/FELT" + } + } + } + }, + "nonces": { + "title": "Nonces", + "type": "array", + "items": { + "title": "Nonce update", + "description": "The updated nonce per contract address", + "type": "object", + "properties": { + "contract_address": { + "title": "Contract address", + "description": "The address of the contract", + "$ref": "#/components/schemas/ADDRESS" + }, + "nonce": { + "title": "Nonce", + "description": "The nonce for the given address at the end of the block", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": [ + "storage_diffs", + "deprecated_declared_classes", + "declared_classes", + "replaced_classes", + "deployed_contracts", + "nonces" + ] + }, + "PENDING_STATE_UPDATE": { + "title": "Pending state update", + "description": "Pending state update", + "type": "object", + "properties": { + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": [ + "old_root", + "state_diff" + ], + "additionalProperties": false + }, + "STATE_UPDATE": { + "title": "State update", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "old_root": { + "title": "Old root", + "description": "The previous global state root", + "$ref": "#/components/schemas/FELT" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "state_diff": { + "title": "State diff", + "$ref": "#/components/schemas/STATE_DIFF" + } + }, + "required": [ + "state_diff", + "block_hash", + "old_root", + "new_root" + ] + }, + "ADDRESS": { + "title": "Address", + "$ref": "#/components/schemas/FELT" + }, + "STORAGE_KEY": { + "type": "string", + "title": "Storage key", + "$comment": "A storage key, represented as a string of hex digits", + "description": "A storage key. Represented as up to 62 hex digits, 3 bits, and 5 leading zeroes.", + "pattern": "^0x(0|[0-7]{1}[a-fA-F0-9]{0,62}$)" + }, + "ETH_ADDRESS": { + "title": "Ethereum address", + "type": "string", + "$comment": "An ethereum address", + "description": "an ethereum address represented as 40 hex digits", + "pattern": "^0x[a-fA-F0-9]{40}$" + }, + "TXN_HASH": { + "$ref": "#/components/schemas/FELT", + "description": "The hash of a Starknet transaction", + "title": "Transaction hash" + }, + "L1_TXN_HASH": { + "$ref": "#/components/schemas/NUM_AS_HEX", + "description": "The hash of an Ethereum transaction" + }, + "FELT": { + "type": "string", + "title": "Field element", + "description": "A field element. represented by at most 63 hex digits", + "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,62})$" + }, + "BLOCK_NUMBER": { + "title": "Block number", + "description": "The block's number (its height)", + "type": "integer", + "minimum": 0 + }, + "BLOCK_HASH": { + "title": "Block hash", + "$ref": "#/components/schemas/FELT" + }, + "BLOCK_BODY_WITH_TX_HASHES": { + "title": "Block body with transaction hashes", + "type": "object", + "properties": { + "transactions": { + "title": "Transaction hashes", + "description": "The hashes of the transactions included in this block", + "type": "array", + "items": { + "description": "The hash of a single transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + } + }, + "required": [ + "transactions" + ] + }, + "BLOCK_BODY_WITH_TXS": { + "title": "Block body with transactions", + "type": "object", + "properties": { + "transactions": { + "title": "Transactions", + "description": "The transactions in this block", + "type": "array", + "items": { + "title": "transactions in block", + "type": "object", + "allOf": [ + { + "title": "transaction", + "$ref": "#/components/schemas/TXN" + }, + { + "type": "object", + "properties": { + "transaction_hash": { + "title": "transaction hash", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + ] + } + } + }, + "required": [ + "transactions" + ] + }, + "BLOCK_BODY_WITH_RECEIPTS": { + "title": "Block body with transactions and receipts", + "type": "object", + "properties": { + "transactions": { + "title": "Transactions", + "description": "The transactions in this block", + "type": "array", + "items": { + "type": "object", + "title": "transaction and receipt", + "properties": { + "transaction": { + "title": "transaction", + "$ref": "#/components/schemas/TXN" + }, + "receipt": { + "title": "receipt", + "$ref": "#/components/schemas/TXN_RECEIPT" + } + }, + "required": [ + "transaction", + "receipt" + ] + } + } + }, + "required": [ + "transactions" + ] + }, + "BLOCK_HEADER": { + "title": "Block header", + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "block_number": { + "title": "Block number", + "description": "The block number (its height)", + "$ref": "#/components/schemas/BLOCK_NUMBER" + }, + "new_root": { + "title": "New root", + "description": "The new global state root", + "$ref": "#/components/schemas/FELT" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l2_gas_price": { + "title": "L2 gas price", + "description": "The price of l2 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The price of l1 data gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_da_mode": { + "title": "L1 da mode", + "type": "string", + "description": "specifies whether the data of this block is published via blob data or calldata", + "enum": [ + "BLOB", + "CALLDATA" + ] + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "block_hash", + "parent_hash", + "block_number", + "new_root", + "timestamp", + "sequencer_address", + "l1_gas_price", + "l1_data_gas_price", + "l1_da_mode", + "starknet_version" + ] + }, + "PENDING_BLOCK_HEADER": { + "title": "Pending block header", + "type": "object", + "properties": { + "parent_hash": { + "title": "Parent hash", + "description": "The hash of this block's parent", + "$ref": "#/components/schemas/BLOCK_HASH" + }, + "timestamp": { + "title": "Timestamp", + "description": "The time in which the block was created, encoded in Unix time", + "type": "integer", + "minimum": 0 + }, + "sequencer_address": { + "title": "Sequencer address", + "description": "The StarkNet identity of the sequencer submitting this block", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The price of l1 gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The price of l1 data gas in the block", + "$ref": "#/components/schemas/RESOURCE_PRICE" + }, + "l1_da_mode": { + "title": "L1 da mode", + "type": "string", + "description": "specifies whether the data of this block is published via blob data or calldata", + "enum": [ + "BLOB", + "CALLDATA" + ] + }, + "starknet_version": { + "title": "Starknet version", + "description": "Semver of the current Starknet protocol", + "type": "string" + } + }, + "required": [ + "parent_hash", + "timestamp", + "sequencer_address", + "l1_gas_price", + "l1_data_gas_price", + "l1_da_mode", + "starknet_version" + ], + "not": { + "required": [ + "block_hash", + "block_number", + "new_root" + ] + } + }, + "BLOCK_WITH_TX_HASHES": { + "title": "Block with transaction hashes", + "description": "The block object", + "allOf": [ + { + "title": "Block status", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": [ + "status" + ] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transaction hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + } + ] + }, + "BLOCK_WITH_TXS": { + "title": "Block with transactions", + "description": "The block object", + "allOf": [ + { + "title": "block with txs", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": [ + "status" + ] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + } + ] + }, + "BLOCK_WITH_RECEIPTS": { + "title": "Block with transactions and receipts", + "description": "The block object", + "allOf": [ + { + "title": "block with txs", + "type": "object", + "properties": { + "status": { + "title": "Status", + "$ref": "#/components/schemas/BLOCK_STATUS" + } + }, + "required": [ + "status" + ] + }, + { + "title": "Block header", + "$ref": "#/components/schemas/BLOCK_HEADER" + }, + { + "title": "Block body with transactions and receipts", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_RECEIPTS" + } + ] + }, + "PENDING_BLOCK_WITH_TX_HASHES": { + "title": "Pending block with transaction hashes", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions hashes", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TX_HASHES" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "PENDING_BLOCK_WITH_TXS": { + "title": "Pending block with transactions", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_TXS" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "PENDING_BLOCK_WITH_RECEIPTS": { + "title": "Pending block with transactions and receipts", + "description": "The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization.", + "allOf": [ + { + "title": "Block body with transactions and receipts", + "$ref": "#/components/schemas/BLOCK_BODY_WITH_RECEIPTS" + }, + { + "title": "Pending block header", + "$ref": "#/components/schemas/PENDING_BLOCK_HEADER" + } + ] + }, + "DEPLOYED_CONTRACT_ITEM": { + "title": "Deployed contract item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The address of the contract", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the contract code", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "address", + "class_hash" + ] + }, + "CONTRACT_STORAGE_DIFF_ITEM": { + "title": "Contract storage diff item", + "type": "object", + "properties": { + "address": { + "title": "Address", + "description": "The contract address for which the storage changed", + "$ref": "#/components/schemas/FELT" + }, + "storage_entries": { + "title": "Storage entries", + "description": "The changes in the storage of the contract", + "type": "array", + "items": { + "title": "Storage diff item", + "type": "object", + "properties": { + "key": { + "title": "Key", + "description": "The key of the changed value", + "$ref": "#/components/schemas/FELT" + }, + "value": { + "title": "Value", + "description": "The new value applied to the given address", + "$ref": "#/components/schemas/FELT" + } + } + } + } + }, + "required": [ + "address", + "storage_entries" + ] + }, + "TXN": { + "title": "Transaction", + "description": "The transaction schema, as it appears inside a block", + "oneOf": [ + { + "title": "Invoke transaction", + "$ref": "#/components/schemas/INVOKE_TXN" + }, + { + "title": "L1 handler transaction", + "$ref": "#/components/schemas/L1_HANDLER_TXN" + }, + { + "title": "Declare transaction", + "$ref": "#/components/schemas/DECLARE_TXN" + }, + { + "title": "Deploy transaction", + "$ref": "#/components/schemas/DEPLOY_TXN" + }, + { + "title": "Deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + } + ] + }, + "SIGNATURE": { + "title": "Signature", + "description": "A transaction signature", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "DECLARE_TXN": { + "title": "Declare transaction", + "oneOf": [ + { + "title": "Declare transaction V0", + "$ref": "#/components/schemas/DECLARE_TXN_V0" + }, + { + "title": "Declare transaction V1", + "$ref": "#/components/schemas/DECLARE_TXN_V1" + }, + { + "title": "Declare transaction V2", + "$ref": "#/components/schemas/DECLARE_TXN_V2" + }, + { + "title": "Declare transaction V3", + "$ref": "#/components/schemas/DECLARE_TXN_V3" + } + ] + }, + "DECLARE_TXN_V0": { + "title": "Declare Contract Transaction V0", + "description": "Declare Contract Transaction V0", + "allOf": [ + { + "type": "object", + "title": "Declare txn v0", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x0", + "0x100000000000000000000000000000000" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V1": { + "title": "Declare Contract Transaction V1", + "description": "Declare Contract Transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x1", + "0x100000000000000000000000000000001" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "nonce", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V2": { + "title": "Declare Transaction V2", + "description": "Declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x2", + "0x100000000000000000000000000000002" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "class_hash" + ] + } + ] + }, + "DECLARE_TXN_V3": { + "title": "Declare Transaction V3", + "description": "Declare Contract Transaction V3", + "allOf": [ + { + "type": "object", + "title": "Declare txn v3", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x3", + "0x100000000000000000000000000000003" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "version", + "signature", + "nonce", + "class_hash", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "BROADCASTED_TXN": { + "oneOf": [ + { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + }, + { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + ] + }, + "BROADCASTED_INVOKE_TXN": { + "title": "Broadcasted invoke transaction", + "$ref": "#/components/schemas/INVOKE_TXN" + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "title": "Broadcasted deploy account transaction", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN" + }, + "BROADCASTED_DECLARE_TXN": { + "title": "Broadcasted declare transaction", + "oneOf": [ + { + "title": "Broadcasted declare transaction V1", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V1" + }, + { + "title": "Broadcasted declare transaction V2", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V2" + }, + { + "title": "Broadcasted declare transaction V3", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN_V3" + } + ] + }, + "BROADCASTED_DECLARE_TXN_V1": { + "title": "Broadcasted declare contract transaction V1", + "allOf": [ + { + "type": "object", + "title": "Declare txn v1", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x1", + "0x100000000000000000000000000000001" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/DEPRECATED_CONTRACT_CLASS" + } + }, + "required": [ + "type", + "sender_address", + "max_fee", + "version", + "signature", + "nonce", + "contract_class" + ] + } + ] + }, + "BROADCASTED_DECLARE_TXN_V2": { + "title": "Broadcasted declare Transaction V2", + "description": "Broadcasted declare Contract Transaction V2", + "allOf": [ + { + "type": "object", + "title": "Declare txn v2", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x2", + "0x100000000000000000000000000000002" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/CONTRACT_CLASS" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "max_fee", + "version", + "signature", + "nonce", + "contract_class" + ] + } + ] + }, + "BROADCASTED_DECLARE_TXN_V3": { + "title": "Broadcasted declare Transaction V3", + "description": "Broadcasted declare Contract Transaction V3", + "allOf": [ + { + "type": "object", + "title": "Declare txn v3", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + }, + "sender_address": { + "title": "Sender address", + "description": "The address of the account contract sending the declaration transaction", + "$ref": "#/components/schemas/ADDRESS" + }, + "compiled_class_hash": { + "title": "Compiled class hash", + "description": "The hash of the Cairo assembly resulting from the Sierra compilation", + "$ref": "#/components/schemas/FELT" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x3", + "0x100000000000000000000000000000003" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_class": { + "title": "Contract class", + "description": "The class to be declared", + "$ref": "#/components/schemas/CONTRACT_CLASS" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "compiled_class_hash", + "version", + "signature", + "nonce", + "contract_class", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "DEPLOY_ACCOUNT_TXN": { + "title": "Deploy account transaction", + "description": "deploys a new account contract", + "oneOf": [ + { + "title": "Deploy account V1", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_V1" + }, + { + "title": "Deploy account V3", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_V3" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_V1": { + "title": "Deploy account transaction", + "description": "Deploys an account contract, charges fee from the pre-funded account addresses", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x1", + "0x100000000000000000000000000000001" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_address_salt": { + "title": "Contract address salt", + "description": "The salt for the address of the deployed contract", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "description": "The parameters passed to the constructor", + "title": "Constructor calldata", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "max_fee", + "version", + "signature", + "nonce", + "type", + "contract_address_salt", + "constructor_calldata", + "class_hash" + ] + }, + "DEPLOY_ACCOUNT_TXN_V3": { + "title": "Deploy account transaction", + "description": "Deploys an account contract, charges fee from the pre-funded account addresses", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x3", + "0x100000000000000000000000000000003" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "contract_address_salt": { + "title": "Contract address salt", + "description": "The salt for the address of the deployed contract", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "description": "The parameters passed to the constructor", + "title": "Constructor calldata", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "version", + "signature", + "nonce", + "type", + "contract_address_salt", + "constructor_calldata", + "class_hash", + "resource_bounds", + "tip", + "paymaster_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + }, + "DEPLOY_TXN": { + "title": "Deploy Contract Transaction", + "description": "The structure of a deploy transaction. Note that this transaction type is deprecated and will no longer be supported in future versions", + "allOf": [ + { + "type": "object", + "title": "Deploy txn", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "$ref": "#/components/schemas/FELT" + }, + "type": { + "title": "Deploy", + "type": "string", + "enum": [ + "DEPLOY" + ] + }, + "contract_address_salt": { + "description": "The salt for the address of the deployed contract", + "title": "Contract address salt", + "$ref": "#/components/schemas/FELT" + }, + "constructor_calldata": { + "type": "array", + "title": "Constructor calldata", + "description": "The parameters passed to the constructor", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the deployed contract's class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "version", + "type", + "constructor_calldata", + "contract_address_salt", + "class_hash" + ] + } + ] + }, + "INVOKE_TXN_V0": { + "title": "Invoke transaction V0", + "description": "invokes a specific function in the desired contract (not necessarily an account)", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x0", + "0x100000000000000000000000000000000" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "type", + "contract_address", + "entry_point_selector", + "calldata", + "max_fee", + "version", + "signature" + ] + }, + "INVOKE_TXN_V1": { + "title": "Invoke transaction V1", + "description": "initiates a transaction from a given account", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + }, + "sender_address": { + "title": "sender address", + "$ref": "#/components/schemas/ADDRESS" + }, + "calldata": { + "type": "array", + "title": "calldata", + "description": "The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector)", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "max_fee": { + "title": "Max fee", + "$ref": "#/components/schemas/FELT", + "description": "The maximal fee that can be charged for including the transaction" + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x1", + "0x100000000000000000000000000000001" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "sender_address", + "calldata", + "max_fee", + "version", + "signature", + "nonce" + ] + } + ] + }, + "INVOKE_TXN_V3": { + "title": "Invoke transaction V3", + "description": "initiates a transaction from a given account", + "allOf": [ + { + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + }, + "sender_address": { + "title": "sender address", + "$ref": "#/components/schemas/ADDRESS" + }, + "calldata": { + "type": "array", + "title": "calldata", + "description": "The data expected by the account's `execute` function (in most usecases, this includes the called contract address and a function selector)", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x3", + "0x100000000000000000000000000000003" + ] + }, + "signature": { + "title": "Signature", + "$ref": "#/components/schemas/SIGNATURE" + }, + "nonce": { + "title": "Nonce", + "$ref": "#/components/schemas/FELT" + }, + "resource_bounds": { + "title": "Resource bounds", + "description": "resource bounds for the transaction execution", + "$ref": "#/components/schemas/RESOURCE_BOUNDS_MAPPING" + }, + "tip": { + "title": "Tip", + "$ref": "#/components/schemas/u64", + "description": "the tip for the transaction" + }, + "paymaster_data": { + "title": "Paymaster data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to allow the paymaster to pay for the transaction in native tokens" + }, + "account_deployment_data": { + "title": "Account deployment data", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + }, + "description": "data needed to deploy the account contract from which this tx will be initiated" + }, + "nonce_data_availability_mode": { + "title": "Nonce DA mode", + "description": "The storage domain of the account's nonce (an account has a nonce per DA mode)", + "$ref": "#/components/schemas/DA_MODE" + }, + "fee_data_availability_mode": { + "title": "Fee DA mode", + "description": "The storage domain of the account's balance from which fee will be charged", + "$ref": "#/components/schemas/DA_MODE" + } + }, + "required": [ + "type", + "sender_address", + "calldata", + "version", + "signature", + "nonce", + "resource_bounds", + "tip", + "paymaster_data", + "account_deployment_data", + "nonce_data_availability_mode", + "fee_data_availability_mode" + ] + } + ] + }, + "INVOKE_TXN": { + "title": "Invoke transaction", + "description": "Initiate a transaction from an account", + "oneOf": [ + { + "title": "Invoke transaction V0", + "$ref": "#/components/schemas/INVOKE_TXN_V0" + }, + { + "title": "Invoke transaction V1", + "$ref": "#/components/schemas/INVOKE_TXN_V1" + }, + { + "title": "Invoke transaction V3", + "$ref": "#/components/schemas/INVOKE_TXN_V3" + } + ] + }, + "L1_HANDLER_TXN": { + "title": "L1 Handler transaction", + "allOf": [ + { + "type": "object", + "title": "L1 handler transaction", + "description": "a call to an l1_handler on an L2 contract induced by a message from L1", + "properties": { + "version": { + "title": "Version", + "description": "Version of the transaction scheme", + "type": "string", + "enum": [ + "0x0" + ] + }, + "type": { + "title": "type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + }, + "nonce": { + "title": "Nonce", + "description": "The L1->L2 message nonce field of the SN Core L1 contract at the time the transaction was sent", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "version", + "type", + "nonce" + ] + }, + { + "title": "Function call", + "$ref": "#/components/schemas/FUNCTION_CALL" + } + ] + }, + "COMMON_RECEIPT_PROPERTIES": { + "allOf": [ + { + "title": "Common receipt properties", + "description": "Common properties for a transaction receipt", + "type": "object", + "properties": { + "transaction_hash": { + "title": "Transaction hash", + "$ref": "#/components/schemas/TXN_HASH", + "description": "The hash identifying the transaction" + }, + "actual_fee": { + "title": "Actual fee", + "$ref": "#/components/schemas/FEE_PAYMENT", + "description": "The fee that was charged by the sequencer" + }, + "finality_status": { + "title": "Finality status", + "description": "finality status of the tx", + "$ref": "#/components/schemas/TXN_FINALITY_STATUS" + }, + "messages_sent": { + "type": "array", + "title": "Messages sent", + "items": { + "$ref": "#/components/schemas/MSG_TO_L1" + } + }, + "events": { + "description": "The events emitted as part of this transaction", + "title": "Events", + "type": "array", + "items": { + "$ref": "#/components/schemas/EVENT" + } + }, + "execution_resources": { + "title": "Execution resources", + "description": "The resources consumed by the transaction", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + } + }, + "required": [ + "transaction_hash", + "actual_fee", + "finality_status", + "messages_sent", + "events", + "execution_resources" + ] + }, + { + "oneOf": [ + { + "title": "Successful Common receipt properties", + "description": "Common properties for a transaction receipt that was executed successfully", + "type": "object", + "properties": { + "execution_status": { + "title": "Execution status", + "type": "string", + "enum": [ + "SUCCEEDED" + ], + "description": "The execution status of the transaction" + } + }, + "required": [ + "execution_status" + ] + }, + { + "title": "Reverted Common receipt properties", + "description": "Common properties for a transaction receipt that was reverted", + "type": "object", + "properties": { + "execution_status": { + "title": "Execution status", + "type": "string", + "enum": [ + "REVERTED" + ], + "description": "The execution status of the transaction" + }, + "revert_reason": { + "title": "Revert reason", + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + } + }, + "required": [ + "execution_status", + "revert_reason" + ] + } + ] + } + ] + }, + "INVOKE_TXN_RECEIPT": { + "title": "Invoke Transaction Receipt", + "allOf": [ + { + "title": "Type", + "type": "object", + "properties": { + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DECLARE_TXN_RECEIPT": { + "title": "Declare Transaction Receipt", + "allOf": [ + { + "title": "Declare txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Declare", + "type": "string", + "enum": [ + "DECLARE" + ] + } + }, + "required": [ + "type" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "DEPLOY_ACCOUNT_TXN_RECEIPT": { + "title": "Deploy Account Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "DeployAccount txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy account", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "contract_address" + ] + } + ] + }, + "DEPLOY_TXN_RECEIPT": { + "title": "Deploy Transaction Receipt", + "allOf": [ + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + }, + { + "title": "Deploy txn receipt", + "type": "object", + "properties": { + "type": { + "title": "Deploy", + "type": "string", + "enum": [ + "DEPLOY" + ] + }, + "contract_address": { + "title": "Contract address", + "description": "The address of the deployed contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "type", + "contract_address" + ] + } + ] + }, + "L1_HANDLER_TXN_RECEIPT": { + "title": "L1 Handler Transaction Receipt", + "description": "receipt for l1 handler transaction", + "allOf": [ + { + "title": "Transaction type", + "type": "object", + "properties": { + "type": { + "title": "type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + }, + "message_hash": { + "title": "Message hash", + "description": "The message hash as it appears on the L1 core contract", + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "type", + "message_hash" + ] + }, + { + "title": "Common receipt properties", + "$ref": "#/components/schemas/COMMON_RECEIPT_PROPERTIES" + } + ] + }, + "TXN_RECEIPT": { + "title": "Transaction Receipt", + "oneOf": [ + { + "title": "Invoke transaction receipt", + "$ref": "#/components/schemas/INVOKE_TXN_RECEIPT" + }, + { + "title": "L1 handler transaction receipt", + "$ref": "#/components/schemas/L1_HANDLER_TXN_RECEIPT" + }, + { + "title": "Declare transaction receipt", + "$ref": "#/components/schemas/DECLARE_TXN_RECEIPT" + }, + { + "title": "Deploy transaction receipt", + "$ref": "#/components/schemas/DEPLOY_TXN_RECEIPT" + }, + { + "title": "Deploy account transaction receipt", + "$ref": "#/components/schemas/DEPLOY_ACCOUNT_TXN_RECEIPT" + } + ] + }, + "TXN_RECEIPT_WITH_BLOCK_INFO": { + "title": "Transaction receipt with block info", + "allOf": [ + { + "title": "Transaction receipt", + "$ref": "#/components/schemas/TXN_RECEIPT" + }, + { + "type": "object", + "properties": { + "block_hash": { + "title": "Block hash", + "$ref": "#/components/schemas/BLOCK_HASH", + "description": "If this field is missing, it means the receipt belongs to the pending block" + }, + "block_number": { + "title": "Block number", + "$ref": "#/components/schemas/BLOCK_NUMBER", + "description": "If this field is missing, it means the receipt belongs to the pending block" + } + } + } + ] + }, + "MSG_TO_L1": { + "title": "Message to L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L2 contract sending the message", + "$ref": "#/components/schemas/FELT" + }, + "to_address": { + "title": "To address", + "description": "The target L1 address the message is sent to", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "from_address", + "to_address", + "payload" + ] + }, + "MSG_FROM_L1": { + "title": "Message from L1", + "type": "object", + "properties": { + "from_address": { + "description": "The address of the L1 contract sending the message", + "$ref": "#/components/schemas/ETH_ADDRESS" + }, + "to_address": { + "title": "To address", + "description": "The target L2 address the message is sent to", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Selector", + "description": "The selector of the l1_handler in invoke in the target contract", + "$ref": "#/components/schemas/FELT" + }, + "payload": { + "description": "The payload of the message", + "title": "Payload", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "from_address", + "to_address", + "payload", + "entry_point_selector" + ] + }, + "TXN_STATUS_RESULT": { + "title": "Transaction status result", + "description": "Transaction status result, including finality status and execution status", + "schema": { + "title": "Transaction status result", + "type": "object", + "properties": { + "finality_status": { + "title": "finality status", + "$ref": "#/components/schemas/TXN_STATUS" + }, + "execution_status": { + "title": "execution status", + "$ref": "#/components/schemas/TXN_EXECUTION_STATUS" + }, + "failure_reason": { + "title": "failure reason", + "description": "the failure reason, only appears if finality_status is REJECTED or execution_status is REVERTED", + "type": "string" + } + }, + "required": [ + "finality_status" + ] + } + }, + "TXN_STATUS": { + "title": "Transaction status", + "type": "string", + "enum": [ + "RECEIVED", + "REJECTED", + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1" + ], + "description": "The finality status of the transaction, including the case the txn is still in the mempool or failed validation during the block construction phase" + }, + "TXN_FINALITY_STATUS": { + "title": "Finality status", + "type": "string", + "enum": [ + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1" + ], + "description": "The finality status of the transaction" + }, + "TXN_EXECUTION_STATUS": { + "title": "Execution status", + "type": "string", + "enum": [ + "SUCCEEDED", + "REVERTED" + ], + "description": "The execution status of the transaction" + }, + "TXN_TYPE": { + "title": "Transaction type", + "type": "string", + "enum": [ + "DECLARE", + "DEPLOY", + "DEPLOY_ACCOUNT", + "INVOKE", + "L1_HANDLER" + ], + "description": "The type of the transaction" + }, + "BLOCK_STATUS": { + "title": "Block status", + "type": "string", + "enum": [ + "PENDING", + "ACCEPTED_ON_L2", + "ACCEPTED_ON_L1", + "REJECTED" + ], + "description": "The status of the block" + }, + "FUNCTION_CALL": { + "title": "Function call", + "type": "object", + "description": "Function call information", + "properties": { + "contract_address": { + "title": "Contract address", + "$ref": "#/components/schemas/ADDRESS" + }, + "entry_point_selector": { + "title": "Entry point selector", + "$ref": "#/components/schemas/FELT" + }, + "calldata": { + "title": "Calldata", + "type": "array", + "description": "The parameters passed to the function", + "items": { + "$ref": "#/components/schemas/FELT" + } + } + }, + "required": [ + "contract_address", + "entry_point_selector", + "calldata" + ] + }, + "CONTRACT_CLASS": { + "title": "Contract class", + "type": "object", + "properties": { + "sierra_program": { + "title": "Sierra program", + "type": "array", + "description": "The list of Sierra instructions of which the program consists", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "contract_class_version": { + "title": "Contract class version", + "type": "string", + "description": "The version of the contract class object. Currently, the Starknet OS supports version 0.1.0" + }, + "entry_points_by_type": { + "title": "Entry points by type", + "type": "object", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Constructor", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "EXTERNAL": { + "title": "External", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "title": "L1 handler", + "type": "array", + "items": { + "$ref": "#/components/schemas/SIERRA_ENTRY_POINT" + } + } + }, + "required": [ + "CONSTRUCTOR", + "EXTERNAL", + "L1_HANDLER" + ] + }, + "abi": { + "title": "ABI", + "type": "string", + "description": "The class ABI, as supplied by the user declaring the class" + } + }, + "required": [ + "sierra_program", + "contract_class_version", + "entry_points_by_type" + ] + }, + "DEPRECATED_CONTRACT_CLASS": { + "title": "Deprecated contract class", + "description": "The definition of a StarkNet contract class", + "type": "object", + "properties": { + "program": { + "type": "string", + "title": "Program", + "description": "A base64 representation of the compressed program code", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" + }, + "entry_points_by_type": { + "type": "object", + "title": "Deprecated entry points by type", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Deprecated constructor", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "EXTERNAL": { + "type": "array", + "title": "Deprecated external", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "type": "array", + "title": "Deprecated L1 handler", + "items": { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + } + } + }, + "abi": { + "title": "Contract ABI", + "$ref": "#/components/schemas/CONTRACT_ABI" + } + }, + "required": [ + "program", + "entry_points_by_type" + ] + }, + "DEPRECATED_CAIRO_ENTRY_POINT": { + "title": "Deprecated Cairo entry point", + "type": "object", + "properties": { + "offset": { + "title": "Offset", + "description": "The offset of the entry point in the program", + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "offset", + "selector" + ] + }, + "SIERRA_ENTRY_POINT": { + "title": "Sierra entry point", + "type": "object", + "properties": { + "selector": { + "title": "Selector", + "description": "A unique identifier of the entry point (function) in the program", + "$ref": "#/components/schemas/FELT" + }, + "function_idx": { + "title": "Function index", + "description": "The index of the function in the program", + "type": "integer" + } + }, + "required": [ + "selector", + "function_idx" + ] + }, + "CONTRACT_ABI": { + "title": "Contract ABI", + "type": "array", + "items": { + "$ref": "#/components/schemas/CONTRACT_ABI_ENTRY" + } + }, + "CONTRACT_ABI_ENTRY": { + "title": "Contract ABI entry", + "oneOf": [ + { + "title": "Function ABI entry", + "$ref": "#/components/schemas/FUNCTION_ABI_ENTRY" + }, + { + "title": "Event ABI entry", + "$ref": "#/components/schemas/EVENT_ABI_ENTRY" + }, + { + "title": "Struct ABI entry", + "$ref": "#/components/schemas/STRUCT_ABI_ENTRY" + } + ] + }, + "STRUCT_ABI_TYPE": { + "title": "Struct ABI type", + "type": "string", + "enum": [ + "struct" + ] + }, + "EVENT_ABI_TYPE": { + "title": "Event ABI type", + "type": "string", + "enum": [ + "event" + ] + }, + "FUNCTION_ABI_TYPE": { + "title": "Function ABI type", + "type": "string", + "enum": [ + "function", + "l1_handler", + "constructor" + ] + }, + "STRUCT_ABI_ENTRY": { + "title": "Struct ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Struct ABI type", + "$ref": "#/components/schemas/STRUCT_ABI_TYPE" + }, + "name": { + "title": "Struct name", + "description": "The struct name", + "type": "string" + }, + "size": { + "title": "Size", + "type": "integer", + "minimum": 1 + }, + "members": { + "type": "array", + "title": "Members", + "items": { + "$ref": "#/components/schemas/STRUCT_MEMBER" + } + } + }, + "required": [ + "type", + "name", + "size", + "members" + ] + }, + "STRUCT_MEMBER": { + "title": "Struct member", + "allOf": [ + { + "title": "Typed parameter", + "$ref": "#/components/schemas/TYPED_PARAMETER" + }, + { + "type": "object", + "title": "Offset", + "properties": { + "offset": { + "title": "Offset", + "description": "offset of this property within the struct", + "type": "integer" + } + } + } + ] + }, + "EVENT_ABI_ENTRY": { + "title": "Event ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Event ABI type", + "$ref": "#/components/schemas/EVENT_ABI_TYPE" + }, + "name": { + "title": "Event name", + "description": "The event name", + "type": "string" + }, + "keys": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "data": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + } + }, + "required": [ + "type", + "name", + "keys", + "data" + ] + }, + "FUNCTION_STATE_MUTABILITY": { + "title": "Function state mutability type", + "type": "string", + "enum": [ + "view" + ] + }, + "FUNCTION_ABI_ENTRY": { + "title": "Function ABI entry", + "type": "object", + "properties": { + "type": { + "title": "Function ABI type", + "$ref": "#/components/schemas/FUNCTION_ABI_TYPE" + }, + "name": { + "title": "Function name", + "description": "The function name", + "type": "string" + }, + "inputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "outputs": { + "type": "array", + "title": "Typed parameter", + "items": { + "$ref": "#/components/schemas/TYPED_PARAMETER" + } + }, + "stateMutability": { + "title": "Function state mutability", + "$ref": "#/components/schemas/FUNCTION_STATE_MUTABILITY" + } + }, + "required": [ + "type", + "name", + "inputs", + "outputs" + ] + }, + "TYPED_PARAMETER": { + "title": "Typed parameter", + "type": "object", + "properties": { + "name": { + "title": "Parameter name", + "description": "The parameter's name", + "type": "string" + }, + "type": { + "title": "Parameter type", + "description": "The parameter's type", + "type": "string" + } + }, + "required": [ + "name", + "type" + ] + }, + "SIMULATION_FLAG_FOR_ESTIMATE_FEE": { + "type": "string", + "enum": [ + "SKIP_VALIDATE" + ], + "description": "Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally" + }, + "PRICE_UNIT": { + "title": "price unit", + "type": "string", + "enum": [ + "WEI", + "FRI" + ] + }, + "FEE_ESTIMATE": { + "title": "Fee estimation", + "type": "object", + "properties": { + "l1_gas_consumed": { + "title": "L1 gas consumed", + "description": "The Ethereum gas consumption of the transaction, charged for L1->L2 messages and, depending on the block's DA_MODE, state diffs", + "$ref": "#/components/schemas/FELT" + }, + "l1_gas_price": { + "title": "L1 gas price", + "description": "The gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "l2_gas_consumed": { + "title": "L2 gas consumed", + "description": "The L2 gas consumption of the transaction", + "$ref": "#/components/schemas/FELT" + }, + "l2_gas_price": { + "title": "L2 gas price", + "description": "The L2 gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "l1_data_gas_consumed": { + "title": "L1 data gas consumed", + "description": "The Ethereum data gas consumption of the transaction", + "$ref": "#/components/schemas/FELT" + }, + "l1_data_gas_price": { + "title": "L1 data gas price", + "description": "The data gas price (in wei or fri, depending on the tx version) that was used in the cost estimation", + "$ref": "#/components/schemas/FELT" + }, + "overall_fee": { + "title": "Overall fee", + "description": "The estimated fee for the transaction (in wei or fri, depending on the tx version), equals to gas_consumed*gas_price + data_gas_consumed*data_gas_price", + "$ref": "#/components/schemas/FELT" + }, + "unit": { + "title": "Fee unit", + "description": "units in which the fee is given", + "$ref": "#/components/schemas/PRICE_UNIT" + } + }, + "required": [ + "gas_consumed", + "gas_price", + "data_gas_consumed", + "data_gas_price", + "overall_fee", + "unit" + ] + }, + "FEE_PAYMENT": { + "title": "Fee Payment", + "description": "fee payment info as it appears in receipts", + "type": "object", + "properties": { + "amount": { + "title": "Amount", + "description": "amount paid", + "$ref": "#/components/schemas/FELT" + }, + "unit": { + "title": "Fee unit", + "description": "units in which the fee is given", + "$ref": "#/components/schemas/PRICE_UNIT" + } + }, + "required": [ + "amount", + "unit" + ] + }, + "DA_MODE": { + "title": "DA mode", + "type": "string", + "description": "Specifies a storage domain in Starknet. Each domain has different gurantess regarding availability", + "enum": [ + "L1", + "L2" + ] + }, + "RESOURCE_BOUNDS_MAPPING": { + "type": "object", + "properties": { + "l1_gas": { + "title": "L1 Gas", + "description": "The max amount and max price per unit of L1 gas used in this tx", + "$ref": "#/components/schemas/RESOURCE_BOUNDS" + }, + "l2_gas": { + "title": "L2 Gas", + "description": "The max amount and max price per unit of L2 gas used in this tx", + "$ref": "#/components/schemas/RESOURCE_BOUNDS" + } + }, + "required": [ + "l1_gas", + "l2_gas" + ] + }, + "RESOURCE_BOUNDS": { + "type": "object", + "properties": { + "max_amount": { + "title": "max amount", + "description": "the max amount of the resource that can be used in the tx", + "$ref": "#/components/schemas/u64" + }, + "max_price_per_unit": { + "title": "max price", + "description": "the max price per unit of this resource for this tx", + "$ref": "#/components/schemas/u128" + } + }, + "required": [ + "max_amount", + "max_price_per_unit" + ] + }, + "RESOURCE_PRICE": { + "type": "object", + "properties": { + "price_in_fri": { + "title": "price in fri", + "description": "the price of one unit of the given resource, denominated in fri (10^-18 strk)", + "$ref": "#/components/schemas/FELT" + }, + "price_in_wei": { + "title": "price in wei", + "description": "the price of one unit of the given resource, denominated in wei", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "price_in_wei", + "price_in_fri" + ] + }, + "EXECUTION_RESOURCES": { + "type": "object", + "title": "Execution resources", + "description": "the resources consumed by the transaction", + "properties": { + "l1_gas": { + "title": "L1Gas", + "description": "l1 gas consumed by this transaction, used for l2-->l1 messages and state updates if blobs are not used", + "type": "integer" + }, + "l1_data_gas": { + "title": "L1DataGas", + "description": "data gas consumed by this transaction, 0 if blobs are not used", + "type": "integer" + }, + "l2_gas": { + "title": "L2Gas", + "description": "l2 gas consumed by this transaction, used for computation and calldata", + "type": "integer" + } + } + }, + "MERKLE_NODE": { + "type": "object", + "properties": { + "path": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "$ref": "#/components/schemas/FELT" + }, + "children_hashes": { + "type": "object", + "description": "the hash of the child nodes, if not present then the node is a leaf", + "properties": { + "left": { + "$ref": "#/components/schemas/FELT" + }, + "right": { + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "left", + "right" + ] + } + }, + "required": [ + "path", + "length", + "value" + ] + }, + "NODE_HASH_TO_NODE_MAPPING": { + "description": "a node_hash -> node mapping of all the nodes in the union of the paths between the requested leaves and the root (for each node present, its sibling is also present)", + "type": "array", + "items": { + "type": "object", + "properties": { + "node_hash": { + "$ref": "#/components/schemas/FELT" + }, + "node": { + "$ref": "#/components/schemas/MERKLE_NODE" + } + }, + "required": [ + "node_hash", + "node" + ] + } + }, + "CONTRACT_EXECUTION_ERROR": { + "description": "structured error that can later be processed by wallets or sdks", + "titel": "contract execution error", + "oneOf": [ + { + "type": "object", + "title": "inner call", + "description": "error frame", + "properties": { + "contract_address": { + "$ref": "#/components/schemas/ADDRESS" + }, + "class_hash": { + "$ref": "#/components/schemas/FELT" + }, + "selector": { + "$ref": "#/components/schemas/FELT" + }, + "error": { + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + } + }, + { + "title": "error message", + "description": "the error raised during execution", + "type": "string" + } + ] + } + }, + "errors": { + "FAILED_TO_RECEIVE_TXN": { + "code": 1, + "message": "Failed to write transaction" + }, + "CONTRACT_NOT_FOUND": { + "code": 20, + "message": "Contract not found" + }, + "BLOCK_NOT_FOUND": { + "code": 24, + "message": "Block not found" + }, + "INVALID_TXN_INDEX": { + "code": 27, + "message": "Invalid transaction index in a block" + }, + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "TXN_HASH_NOT_FOUND": { + "code": 29, + "message": "Transaction hash not found" + }, + "PAGE_SIZE_TOO_BIG": { + "code": 31, + "message": "Requested page size is too big" + }, + "NO_BLOCKS": { + "code": 32, + "message": "There are no blocks" + }, + "INVALID_CONTINUATION_TOKEN": { + "code": 33, + "message": "The supplied continuation token is invalid or unknown" + }, + "TOO_MANY_KEYS_IN_FILTER": { + "code": 34, + "message": "Too many keys provided in a filter" + }, + "CONTRACT_ERROR": { + "code": 40, + "message": "Contract error", + "data": { + "type": "object", + "description": "More data about the execution failure", + "properties": { + "revert_error": { + "title": "revert error", + "description": "the execution trace up to the point of failure", + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + }, + "required": "revert_error" + } + }, + "TRANSACTION_EXECUTION_ERROR": { + "code": 41, + "message": "Transaction execution error", + "data": { + "type": "object", + "description": "More data about the execution failure", + "properties": { + "transaction_index": { + "title": "Transaction index", + "description": "The index of the first transaction failing in a sequence of given transactions", + "type": "integer" + }, + "execution_error": { + "title": "revert error", + "description": "the execution trace up to the point of failure", + "$ref": "#/components/schemas/CONTRACT_EXECUTION_ERROR" + } + }, + "required": [ + "transaction_index", + "execution_error" + ] + } + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_executables.json b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_executables.json new file mode 100644 index 000000000..3232fb45a --- /dev/null +++ b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_executables.json @@ -0,0 +1,1360 @@ +{ + "openrpc": "1.0.0", + "info": { + "version": "0.8.0", + "title": "API for getting Starknet executables from nodes that store compiled artifacts", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_getCompiledCasm", + "summary": "Get the contract class definition in the given block associated with the given hash", + "params": [ + { + "name": "class_hash", + "description": "The hash of the contract class whose CASM will be returned", + "required": true, + "schema": { + "title": "Field element", + "$ref": "#/components/schemas/FELT" + } + } + ], + "result": { + "name": "result", + "description": "The compiled contract class", + "schema": { + "title": "Starknet get compiled CASM result", + "$ref": "#/components/schemas/CASM_COMPILED_CONTRACT_CLASS" + } + }, + "errors": [ + { + "$ref": "#/components/errors/COMPILATION_ERROR" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "CASM_COMPILED_CONTRACT_CLASS": { + "type": "object", + "properties": { + "entry_points_by_type": { + "title": "Entry points by type", + "type": "object", + "properties": { + "CONSTRUCTOR": { + "type": "array", + "title": "Constructor", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + }, + "EXTERNAL": { + "title": "External", + "type": "array", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + }, + "L1_HANDLER": { + "title": "L1 handler", + "type": "array", + "items": { + "$ref": "#/components/schemas/CASM_ENTRY_POINT" + } + } + }, + "required": [ + "CONSTRUCTOR", + "EXTERNAL", + "L1_HANDLER" + ] + }, + "bytecode": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "prime": { + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "compiler_version": { + "type": "string" + }, + "hints": { + "type": "array", + "items": { + "type": "array", + "description": "2-tuple of pc value and an array of hints to execute", + "items": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/HINT" + } + } + ] + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "bytecode_segment_lengths": { + "type": "array", + "description": "a list of sizes of segments in the bytecode, each segment is hashed invidually when computing the bytecode hash", + "items": { + "type": "integer" + } + }, + "required": [ + "prime", + "compiler_version", + "entry_points_by_type", + "bytecode", + "hints" + ] + }, + "CASM_ENTRY_POINT": { + "allOf": [ + { + "$ref": "#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + }, + { + "type": "object", + "properties": { + "builtins": { + "type": "array", + "items": "string" + } + }, + "required": "builtins" + } + ] + }, + "CellRef": { + "title": "CellRef", + "type": "object", + "properties": { + "register": { + "type": "string", + "enum": [ + "AP", + "FP" + ] + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "register", + "offset" + ] + }, + "Deref": { + "type": "object", + "properties": { + "Deref": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "Deref" + ] + }, + "DoubleDeref": { + "title": "DoubleDeref", + "type": "object", + "properties": { + "DoubleDeref": { + "title": "DoubleDeref", + "description": "A (CellRef, offsest) tuple", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/CellRef" + }, + { + "type": "integer" + } + ] + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "DoubleDeref" + ] + }, + "Immediate": { + "title": "Imeediate", + "type": "object", + "properties": { + "Immediate": { + "$ref": "#/components/schemas/NUM_AS_HEX" + } + }, + "required": [ + "Immediate" + ] + }, + "BinOp": { + "title": "BinOperand", + "type": "object", + "properties": { + "BinOp": { + "op": { + "type": "string", + "enum": [ + "Add", + "Mul" + ] + }, + "a": { + "$ref": "#/components/schemas/CellRef" + }, + "b": { + "oneOf": [ + { + "$ref": "#/components/schemas/Deref" + }, + { + "$ref": "#/components/schemas/Immediate" + } + ] + } + } + }, + "required": [ + "BinOp", + "a", + "b" + ] + }, + "ResOperand": { + "oneOf": [ + { + "$ref": "#/components/schemas/Deref" + }, + { + "$ref": "#/components/schemas/DoubleDeref" + }, + { + "$ref": "#/components/schemas/Immediate" + }, + { + "$ref": "#/components/schemas/BinOp" + } + ] + }, + "HINT": { + "oneOf": [ + { + "$ref": "#/components/schemas/DEPRECATED_HINT" + }, + { + "$ref": "#/components/schemas/CORE_HINT" + }, + { + "$ref": "#/components/schemas/STARKNET_HINT" + } + ] + }, + "DEPRECATED_HINT": { + "oneOf": [ + { + "type": "string", + "title": "AssertCurrentAccessIndicesIsEmpty", + "enum": [ + "AssertCurrentAccessIndicesIsEmpty" + ] + }, + { + "type": "object", + "title": "AssertAllAccessesUsed", + "properties": { + "AssertAllAccessesUsed": { + "type": "object", + "properties": { + "n_used_accesses": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "n_used_accesses" + ] + } + }, + "required": [ + "AssertAllAccessesUsed" + ] + }, + { + "type": "string", + "title": "AssertAllKeysUsed", + "enum": [ + "AssertAllKeysUsed" + ] + }, + { + "type": "string", + "title": "AssertLeAssertThirdArcExcluded", + "enum": [ + "AssertLeAssertThirdArcExcluded" + ] + }, + { + "type": "object", + "title": "AssertLtAssertValidInput", + "properties": { + "AssertLtAssertValidInput": { + "type": "object", + "properties": { + "a": { + "$ref": "#/components/schemas/ResOperand" + }, + "b": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "a", + "b" + ] + } + }, + "required": [ + "AssertLtAssertValidInput" + ] + }, + { + "type": "object", + "title": "Felt252DictRead", + "properties": { + "Felt252DictRead": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + }, + "value_dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_ptr", + "key", + "value_dst" + ] + } + }, + "required": [ + "Felt252DictRead" + ] + }, + { + "type": "object", + "title": "Felt252DictWrite", + "properties": { + "Felt252DictWrite": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + }, + "value": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "key", + "value" + ] + } + }, + "required": [ + "Felt252DictWrite" + ] + } + ] + }, + "CORE_HINT": { + "oneOf": [ + { + "type": "object", + "title": "AllocSegment", + "properties": { + "AllocSegment": { + "type": "object", + "properties": { + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dst" + ] + } + }, + "required": [ + "AllocSegment" + ] + }, + { + "type": "object", + "title": "TestLessThan", + "properties": { + "TestLessThan": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThan" + ] + }, + { + "type": "object", + "title": "TestLessThanOrEqual", + "properties": { + "TestLessThanOrEqual": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThanOrEqual" + ] + }, + { + "type": "object", + "title": "TestLessThanOrEqualAddress", + "properties": { + "TestLessThanOrEqualAddress": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "dst" + ] + } + }, + "required": [ + "TestLessThanOrEqualAddress" + ] + }, + { + "type": "object", + "title": "WideMul128", + "properties": { + "WideMul128": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "high": { + "$ref": "#/components/schemas/CellRef" + }, + "low": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "high", + "low" + ] + } + }, + "required": [ + "WideMul128" + ] + }, + { + "type": "object", + "title": "DivMod", + "properties": { + "DivMod": { + "type": "object", + "properties": { + "lhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "rhs": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "lhs", + "rhs", + "quotient", + "remainder" + ] + } + }, + "required": [ + "DivMod" + ] + }, + { + "type": "object", + "title": "Uint256DivMod", + "properties": { + "Uint256DivMod": { + "type": "object", + "properties": { + "dividend0": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend1": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor0": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor1": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient0": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient1": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder0": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dividend0", + "dividend1", + "divisor0", + "divisor1", + "quotient0", + "quotient1", + "remainder0", + "remainder1" + ] + } + }, + "required": [ + "Uint256DivMod" + ] + }, + { + "type": "object", + "title": "Uint512DivModByUint256", + "properties": { + "Uint512DivModByUint256": { + "type": "object", + "properties": { + "dividend0": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend1": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend2": { + "$ref": "#/components/schemas/ResOperand" + }, + "dividend3": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor0": { + "$ref": "#/components/schemas/ResOperand" + }, + "divisor1": { + "$ref": "#/components/schemas/ResOperand" + }, + "quotient0": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient1": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient2": { + "$ref": "#/components/schemas/CellRef" + }, + "quotient3": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder0": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dividend0", + "dividend1", + "dividend2", + "dividend3", + "divisor0", + "divisor1", + "quotient0", + "quotient1", + "quotient2", + "quotient3", + "remainder0", + "remainder1" + ] + } + }, + "required": [ + "Uint512DivModByUint256" + ] + }, + { + "type": "object", + "title": "SquareRoot", + "properties": { + "SquareRoot": { + "type": "object", + "properties": { + "value": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value", + "dst" + ] + } + }, + "required": [ + "SquareRoot" + ] + }, + { + "type": "object", + "title": "Uint256SquareRoot", + "properties": { + "Uint256SquareRoot": { + "type": "object", + "properties": { + "value_low": { + "$ref": "#/components/schemas/ResOperand" + }, + "value_high": { + "$ref": "#/components/schemas/ResOperand" + }, + "sqrt0": { + "$ref": "#/components/schemas/CellRef" + }, + "sqrt1": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder_low": { + "$ref": "#/components/schemas/CellRef" + }, + "remainder_high": { + "$ref": "#/components/schemas/CellRef" + }, + "sqrt_mul_2_minus_remainder_ge_u128": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value_low", + "value_high", + "sqrt0", + "sqrt1", + "remainder_low", + "remainder_high", + "sqrt_mul_2_minus_remainder_ge_u128" + ] + } + }, + "required": [ + "Uint256SquareRoot" + ] + }, + { + "type": "object", + "title": "LinearSplit", + "properties": { + "LinearSplit": { + "type": "object", + "properties": { + "value": { + "$ref": "#/components/schemas/ResOperand" + }, + "scalar": { + "$ref": "#/components/schemas/ResOperand" + }, + "max_x": { + "$ref": "#/components/schemas/ResOperand" + }, + "x": { + "$ref": "#/components/schemas/CellRef" + }, + "y": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "value", + "scalar", + "max_x", + "x", + "y" + ] + } + }, + "required": [ + "LinearSplit" + ] + }, + { + "type": "object", + "title": "AllocFelt252Dict", + "properties": { + "AllocFelt252Dict": { + "type": "object", + "properties": { + "segment_arena_ptr": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "segment_arena_ptr" + ] + } + }, + "required": [ + "AllocFelt252Dict" + ] + }, + { + "type": "object", + "title": "Felt252DictEntryInit", + "properties": { + "Felt252DictEntryInit": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "key": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "key" + ] + } + }, + "required": [ + "Felt252DictEntryInit" + ] + }, + { + "type": "object", + "title": "Felt252DictEntryUpdate", + "properties": { + "Felt252DictEntryUpdate": { + "type": "object", + "properties": { + "dict_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "value": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "dict_ptr", + "value" + ] + } + }, + "required": [ + "Felt252DictEntryUpdate" + ] + }, + { + "type": "object", + "title": "GetSegmentArenaIndex", + "properties": { + "GetSegmentArenaIndex": { + "type": "object", + "properties": { + "dict_end_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "dict_index": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_end_ptr", + "dict_index" + ] + } + }, + "required": [ + "GetSegmentArenaIndex" + ] + }, + { + "type": "object", + "title": "InitSquashData", + "properties": { + "InitSquashData": { + "type": "object", + "properties": { + "dict_access": { + "$ref": "#/components/schemas/ResOperand" + }, + "ptr_diff": { + "$ref": "#/components/schemas/ResOperand" + }, + "n_accesses": { + "$ref": "#/components/schemas/ResOperand" + }, + "big_keys": { + "$ref": "#/components/schemas/CellRef" + }, + "first_key": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "dict_access", + "ptr_diff", + "n_accesses", + "big_keys", + "first_key" + ] + } + }, + "required": [ + "InitSquashData" + ] + }, + { + "type": "object", + "title": "GetCurrentAccessIndex", + "properties": { + "GetCurrentAccessIndex": { + "type": "object", + "properties": { + "range_check_ptr": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "range_check_ptr" + ] + } + }, + "required": [ + "GetCurrentAccessIndex" + ] + }, + { + "type": "object", + "title": "ShouldSkipSquashLoop", + "properties": { + "ShouldSkipSquashLoop": { + "type": "object", + "properties": { + "should_skip_loop": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "should_skip_loop" + ] + } + }, + "required": [ + "ShouldSkipSquashLoop" + ] + }, + { + "type": "object", + "title": "GetCurrentAccessDelta", + "properties": { + "GetCurrentAccessDelta": { + "type": "object", + "properties": { + "index_delta_minus1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "index_delta_minus1" + ] + } + }, + "required": [ + "GetCurrentAccessDelta" + ] + }, + { + "type": "object", + "title": "ShouldContinueSquashLoop", + "properties": { + "ShouldContinueSquashLoop": { + "type": "object", + "properties": { + "should_continue": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "should_continue" + ] + } + }, + "required": [ + "ShouldContinueSquashLoop" + ] + }, + { + "type": "object", + "title": "GetNextDictKey", + "properties": { + "GetNextDictKey": { + "type": "object", + "properties": { + "next_key": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "next_key" + ] + } + }, + "required": [ + "GetNextDictKey" + ] + }, + { + "type": "object", + "title": "AssertLeFindSmallArcs", + "properties": { + "AssertLeFindSmallArcs": { + "type": "object", + "properties": { + "range_check_ptr": { + "$ref": "#/components/schemas/ResOperand" + }, + "a": { + "$ref": "#/components/schemas/ResOperand" + }, + "b": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "range_check_ptr", + "a", + "b" + ] + } + }, + "required": [ + "AssertLeFindSmallArcs" + ] + }, + { + "type": "object", + "title": "AssertLeIsFirstArcExcluded", + "properties": { + "AssertLeIsFirstArcExcluded": { + "type": "object", + "properties": { + "skip_exclude_a_flag": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "skip_exclude_a_flag" + ] + } + }, + "required": [ + "AssertLeIsFirstArcExcluded" + ] + }, + { + "type": "object", + "title": "AssertLeIsSecondArcExcluded", + "properties": { + "AssertLeIsSecondArcExcluded": { + "type": "object", + "properties": { + "skip_exclude_b_minus_a": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "skip_exclude_b_minus_a" + ] + } + }, + "required": [ + "AssertLeIsSecondArcExcluded" + ] + }, + { + "type": "object", + "title": "RandomEcPoint", + "properties": { + "RandomEcPoint": { + "type": "object", + "properties": { + "x": { + "$ref": "#/components/schemas/CellRef" + }, + "y": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "x", + "y" + ] + } + }, + "required": [ + "RandomEcPoint" + ] + }, + { + "type": "object", + "title": "FieldSqrt", + "properties": { + "FieldSqrt": { + "type": "object", + "properties": { + "val": { + "$ref": "#/components/schemas/ResOperand" + }, + "sqrt": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "val", + "sqrt" + ] + } + }, + "required": [ + "FieldSqrt" + ] + }, + { + "type": "object", + "title": "DebugPrint", + "properties": { + "DebugPrint": { + "type": "object", + "properties": { + "start": { + "$ref": "#/components/schemas/ResOperand" + }, + "end": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "start", + "end" + ] + } + }, + "required": [ + "DebugPrint" + ] + }, + { + "type": "object", + "title": "AllocConstantSize", + "properties": { + "AllocConstantSize": { + "type": "object", + "properties": { + "size": { + "$ref": "#/components/schemas/ResOperand" + }, + "dst": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "size", + "dst" + ] + } + }, + "required": [ + "AllocConstantSize" + ] + }, + { + "type": "object", + "title": "U256InvModN", + "properties": { + "U256InvModN": { + "type": "object", + "properties": { + "b0": { + "$ref": "#/components/schemas/ResOperand" + }, + "b1": { + "$ref": "#/components/schemas/ResOperand" + }, + "n0": { + "$ref": "#/components/schemas/ResOperand" + }, + "n1": { + "$ref": "#/components/schemas/ResOperand" + }, + "g0_or_no_inv": { + "$ref": "#/components/schemas/CellRef" + }, + "g1_option": { + "$ref": "#/components/schemas/CellRef" + }, + "s_or_r0": { + "$ref": "#/components/schemas/CellRef" + }, + "s_or_r1": { + "$ref": "#/components/schemas/CellRef" + }, + "t_or_k0": { + "$ref": "#/components/schemas/CellRef" + }, + "t_or_k1": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "b0", + "b1", + "n0", + "n1", + "g0_or_no_inv", + "g1_option", + "s_or_r0", + "s_or_r1", + "t_or_k0", + "t_or_k1" + ] + } + }, + "required": [ + "U256InvModN" + ] + }, + { + "type": "object", + "title": "EvalCircuit", + "properties": { + "EvalCircuit": { + "type": "object", + "properties": { + "n_add_mods": { + "$ref": "#/components/schemas/ResOperand" + }, + "add_mod_builtin": { + "$ref": "#/components/schemas/ResOperand" + }, + "n_mul_mods": { + "$ref": "#/components/schemas/ResOperand" + }, + "mul_mod_builtin": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "n_add_mods", + "add_mod_builtin", + "n_mul_mods", + "mul_mod_builtin" + ] + } + }, + "required": [ + "EvalCircuit" + ] + } + ] + }, + "STARKNET_HINT": { + "oneOf": [ + { + "type": "object", + "title": "SystemCall", + "properties": { + "SystemCall": { + "type": "object", + "properties": { + "system": { + "$ref": "#/components/schemas/ResOperand" + } + }, + "required": [ + "system" + ] + } + }, + "required": [ + "SystemCall" + ] + }, + { + "type": "object", + "title": "Cheatcode", + "properties": { + "Cheatcode": { + "type": "object", + "properties": { + "selector": { + "$ref": "#/components/schemas/NUM_AS_HEX" + }, + "input_start": { + "$ref": "#/components/schemas/ResOperand" + }, + "input_end": { + "$ref": "#/components/schemas/ResOperand" + }, + "output_start": { + "$ref": "#/components/schemas/CellRef" + }, + "output_end": { + "$ref": "#/components/schemas/CellRef" + } + }, + "required": [ + "selector", + "input_start", + "input_end", + "output_start", + "output_end" + ] + } + }, + "required": [ + "Cheatcode" + ] + } + ] + }, + "FELT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/FELT" + }, + "NUM_AS_HEX": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/NUM_AS_HEX" + }, + "DEPRECATED_CAIRO_ENTRY_POINT": { + "$ref": "./starknet_api_openrpc.json#/components/schemas/DEPRECATED_CAIRO_ENTRY_POINT" + } + }, + "errors": { + "CLASS_HASH_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/CLASS_HASH_NOT_FOUND" + }, + "COMPILATION_ERROR": { + "data": { + "type": "object", + "description": "More data about the compilation failure", + "properties": { + "compilation_error": { + "title": "compilation error", + "type": "string" + } + }, + "required": "compilation_error" + } + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_trace_api_openrpc.json b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_trace_api_openrpc.json new file mode 100644 index 000000000..157086fee --- /dev/null +++ b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_trace_api_openrpc.json @@ -0,0 +1,519 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Trace API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_traceTransaction", + "summary": "For a given executed transaction, return the trace of its execution, including internal calls", + "description": "Returns the execution trace of the transaction designated by the input hash", + "params": [ + { + "name": "transaction_hash", + "summary": "The hash of the transaction to trace", + "required": true, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + } + } + ], + "result": { + "name": "trace", + "description": "The function call trace of the transaction designated by the given hash", + "schema": { + "$ref": "#/components/schemas/TRANSACTION_TRACE" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TXN_HASH_NOT_FOUND" + }, + { + "$ref": "#/components/errors/NO_TRACE_AVAILABLE" + } + ] + }, + { + "name": "starknet_simulateTransactions", + "summary": "Simulate a given sequence of transactions on the requested state, and generate the execution traces. Note that some of the transactions may revert, in which case no error is thrown, but revert details can be seen on the returned trace object. . Note that some of the transactions may revert, this will be reflected by the revert_error property in the trace. Other types of failures (e.g. unexpected error or failure in the validation phase) will result in TRANSACTION_EXECUTION_ERROR.", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag, for the block referencing the state or call the transaction on.", + "required": true, + "schema": { + "$ref": "#/components/schemas/BLOCK_ID" + } + }, + { + "name": "transactions", + "description": "The transactions to simulate", + "required": true, + "schema": { + "type": "array", + "description": "a sequence of transactions to simulate, running each transaction on the state resulting from applying all the previous ones", + "items": { + "$ref": "#/components/schemas/BROADCASTED_TXN" + } + } + }, + { + "name": "simulation_flags", + "description": "describes what parts of the transaction should be executed", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SIMULATION_FLAG" + } + } + } + ], + "result": { + "name": "simulated_transactions", + "description": "The execution trace and consuemd resources of the required transactions", + "schema": { + "type": "array", + "items": { + "schema": { + "type": "object", + "properties": { + "transaction_trace": { + "title": "the transaction's trace", + "$ref": "#/components/schemas/TRANSACTION_TRACE" + }, + "fee_estimation": { + "title": "the transaction's resources and fee", + "$ref": "#/components/schemas/FEE_ESTIMATE" + } + } + } + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + }, + { + "$ref": "#/components/errors/TRANSACTION_EXECUTION_ERROR" + } + ] + }, + { + "name": "starknet_traceBlockTransactions", + "summary": "Retrieve traces for all transactions in the given block", + "description": "Returns the execution traces of all transactions included in the given block", + "params": [ + { + "name": "block_id", + "description": "The hash of the requested block, or number (height) of the requested block, or a block tag", + "required": true, + "schema": { + "$ref": "#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "traces", + "description": "The traces of all transactions in the block", + "schema": { + "type": "array", + "items": { + "type": "object", + "description": "A single pair of transaction hash and corresponding trace", + "properties": { + "transaction_hash": { + "$ref": "#/components/schemas/FELT" + }, + "trace_root": { + "$ref": "#/components/schemas/TRANSACTION_TRACE" + } + } + } + } + }, + "errors": [ + { + "$ref": "#/components/errors/BLOCK_NOT_FOUND" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "TRANSACTION_TRACE": { + "oneOf": [ + { + "name": "INVOKE_TXN_TRACE", + "type": "object", + "description": "the execution trace of an invoke transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "execute_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "oneOf": [ + { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + { + "type": "object", + "properties": { + "revert_reason": { + "name": "revert reason", + "description": "the revert reason for the failed execution", + "type": "string" + } + }, + "required": [ + "revert_reason" + ] + } + ] + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "INVOKE" + ] + } + }, + "required": [ + "type", + "execute_invocation", + "execution_resources" + ] + }, + { + "name": "DECLARE_TXN_TRACE", + "type": "object", + "description": "the execution trace of a declare transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "DECLARE" + ] + } + }, + "required": [ + "type", + "execution_resources" + ] + }, + { + "name": "DEPLOY_ACCOUNT_TXN_TRACE", + "type": "object", + "description": "the execution trace of a deploy account transaction", + "properties": { + "validate_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "constructor_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "fee_transfer_invocation": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "DEPLOY_ACCOUNT" + ] + } + }, + "required": [ + "type", + "execution_resources", + "constructor_invocation" + ] + }, + { + "name": "L1_HANDLER_TXN_TRACE", + "type": "object", + "description": "the execution trace of an L1 handler transaction", + "properties": { + "function_invocation": { + "description": "the trace of the __execute__ call or constructor call, depending on the transaction type (none for declare transactions)", + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "state_diff": { + "title": "state_diff", + "description": "the state diffs induced by the transaction", + "$ref": "#/components/schemas/STATE_DIFF" + }, + "execution_resources": { + "title": "Execution resources", + "description": "the resources consumed by the transaction, includes both computation and data", + "$ref": "#/components/schemas/EXECUTION_RESOURCES" + }, + "type": { + "title": "Type", + "type": "string", + "enum": [ + "L1_HANDLER" + ] + } + }, + "required": [ + "type", + "function_invocation", + "execution_resources" + ] + } + ] + }, + "SIMULATION_FLAG": { + "type": "string", + "enum": [ + "SKIP_VALIDATE", + "SKIP_FEE_CHARGE" + ], + "description": "Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally (enough funds are expected to be in the account, and fee will be deducted from the balance before the simulation of the next transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag." + }, + "NESTED_CALL": { + "$ref": "#/components/schemas/FUNCTION_INVOCATION" + }, + "FUNCTION_INVOCATION": { + "allOf": [ + { + "$ref": "#/components/schemas/FUNCTION_CALL" + }, + { + "type": "object", + "properties": { + "caller_address": { + "title": "Caller Address", + "description": "The address of the invoking contract. 0 for the root invocation", + "$ref": "#/components/schemas/FELT" + }, + "class_hash": { + "title": "Class hash", + "description": "The hash of the class being called", + "$ref": "#/components/schemas/FELT" + }, + "entry_point_type": { + "$ref": "#/components/schemas/ENTRY_POINT_TYPE" + }, + "call_type": { + "$ref": "#/components/schemas/CALL_TYPE" + }, + "result": { + "title": "Invocation Result", + "description": "The value returned from the function invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/FELT" + } + }, + "calls": { + "title": "Nested Calls", + "description": "The calls made by this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/NESTED_CALL" + } + }, + "events": { + "title": "Invocation Events", + "description": "The events emitted in this invocation", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_EVENT" + } + }, + "messages": { + "title": "L1 Messages", + "description": "The messages sent by this invocation to L1", + "type": "array", + "items": { + "$ref": "#/components/schemas/ORDERED_MESSAGE" + } + }, + "execution_resources": { + "title": "Computation resources", + "description": "Resources consumed by the internal call. This is named execution_resources for legacy reasons", + "$ref": "#/components/schemas/COMPUTATION_RESOURCES" + } + }, + "required": [ + "caller_address", + "class_hash", + "entry_point_type", + "call_type", + "result", + "calls", + "events", + "messages", + "execution_resources" + ] + } + ] + }, + "ENTRY_POINT_TYPE": { + "type": "string", + "enum": [ + "EXTERNAL", + "L1_HANDLER", + "CONSTRUCTOR" + ] + }, + "CALL_TYPE": { + "type": "string", + "enum": [ + "LIBRARY_CALL", + "CALL", + "DELEGATE" + ] + }, + "ORDERED_EVENT": { + "type": "object", + "title": "orderedEvent", + "description": "an event alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the event within the transaction", + "type": "integer" + } + } + }, + { + "$ref": "#/components/schemas/EVENT" + } + ] + }, + "ORDERED_MESSAGE": { + "type": "object", + "title": "orderedMessage", + "description": "a message alongside its order within the transaction", + "allOf": [ + { + "type": "object", + "properties": { + "order": { + "title": "order", + "description": "the order of the message within the transaction", + "type": "integer" + } + } + }, + { + "$ref": "#/components/schemas/MSG_TO_L1" + } + ] + }, + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "FUNCTION_CALL": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + }, + "EVENT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EVENT_CONTENT" + }, + "MSG_TO_L1": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/MSG_TO_L1" + }, + "BLOCK_ID": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + }, + "FEE_ESTIMATE": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FEE_ESTIMATE" + }, + "BROADCASTED_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_TXN" + }, + "STATE_DIFF": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/STATE_DIFF" + }, + "COMPUTATION_RESOURCES": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/COMPUTATION_RESOURCES" + }, + "EXECUTION_RESOURCES": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EXECUTION_RESOURCES" + } + }, + "errors": { + "NO_TRACE_AVAILABLE": { + "code": 10, + "message": "No trace available for transaction", + "data": { + "type": "object", + "description": "Extra information on why trace is not available. Either it wasn't executed yet (RECEIVED), or the transaction failed (REJECTED)", + "properties": { + "status": { + "type": "string", + "enum": [ + "RECEIVED", + "REJECTED" + ] + } + } + } + }, + "TXN_HASH_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TXN_HASH_NOT_FOUND" + }, + "BLOCK_NOT_FOUND": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/BLOCK_NOT_FOUND" + }, + "TRANSACTION_EXECUTION_ERROR": { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TRANSACTION_EXECUTION_ERROR" + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_write_api.json b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_write_api.json new file mode 100644 index 000000000..52407bb99 --- /dev/null +++ b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_write_api.json @@ -0,0 +1,299 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.7.1", + "title": "StarkNet Node Write API", + "license": {} + }, + "servers": [], + "methods": [ + { + "name": "starknet_addInvokeTransaction", + "summary": "Submit a new transaction to be added to the chain", + "params": [ + { + "name": "invoke_transaction", + "description": "The information needed to invoke the function (or account, for version 1 transactions)", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_INVOKE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the invoke transaction", + "$ref": "#/components/schemas/TXN_HASH" + } + }, + "required": [ + "transaction_hash" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeclareTransaction", + "summary": "Submit a new class declaration transaction", + "params": [ + { + "name": "declare_transaction", + "description": "Declare transaction required to declare a new class on Starknet", + "required": true, + "schema": { + "title": "Declare transaction", + "$ref": "#/components/schemas/BROADCASTED_DECLARE_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the declare transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "class_hash": { + "title": "The hash of the declared class", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "transaction_hash", + "class_hash" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/CLASS_ALREADY_DECLARED" + }, + { + "$ref": "#/components/errors/COMPILATION_FAILED" + }, + { + "$ref": "#/components/errors/COMPILED_CLASS_HASH_MISMATCH" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/CONTRACT_CLASS_SIZE_IS_TOO_LARGE" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_CONTRACT_CLASS_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + }, + { + "name": "starknet_addDeployAccountTransaction", + "summary": "Submit a new deploy account transaction", + "params": [ + { + "name": "deploy_account_transaction", + "description": "The deploy account transaction", + "required": true, + "schema": { + "$ref": "#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + } + } + ], + "result": { + "name": "result", + "description": "The result of the transaction submission", + "schema": { + "type": "object", + "properties": { + "transaction_hash": { + "title": "The hash of the deploy transaction", + "$ref": "#/components/schemas/TXN_HASH" + }, + "contract_address": { + "title": "The address of the new contract", + "$ref": "#/components/schemas/FELT" + } + }, + "required": [ + "transaction_hash", + "contract_address" + ] + } + }, + "errors": [ + { + "$ref": "#/components/errors/INSUFFICIENT_ACCOUNT_BALANCE" + }, + { + "$ref": "#/components/errors/INSUFFICIENT_RESOURCES_FOR_VALIDATE" + }, + { + "$ref": "#/components/errors/INVALID_TRANSACTION_NONCE" + }, + { + "$ref": "#/components/errors/VALIDATION_FAILURE" + }, + { + "$ref": "#/components/errors/NON_ACCOUNT" + }, + { + "$ref": "#/components/errors/CLASS_HASH_NOT_FOUND" + }, + { + "$ref": "#/components/errors/DUPLICATE_TX" + }, + { + "$ref": "#/components/errors/UNSUPPORTED_TX_VERSION" + }, + { + "$ref": "#/components/errors/UNEXPECTED_ERROR" + } + ] + } + ], + "components": { + "contentDescriptors": {}, + "schemas": { + "NUM_AS_HEX": { + "title": "An integer number in hex format (0x...)", + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$" + }, + "SIGNATURE": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/SIGNATURE" + }, + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "TXN_HASH": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + "BROADCASTED_INVOKE_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_INVOKE_TXN" + }, + "BROADCASTED_DECLARE_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_DECLARE_TXN" + }, + "BROADCASTED_DEPLOY_ACCOUNT_TXN": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BROADCASTED_DEPLOY_ACCOUNT_TXN" + }, + "FUNCTION_CALL": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FUNCTION_CALL" + } + }, + "errors": { + "CLASS_HASH_NOT_FOUND": { + "code": 28, + "message": "Class hash not found" + }, + "CLASS_ALREADY_DECLARED": { + "code": 51, + "message": "Class already declared" + }, + "INVALID_TRANSACTION_NONCE": { + "code": 52, + "message": "Invalid transaction nonce" + }, + "INSUFFICIENT_RESOURCES_FOR_VALIDATE": { + "code": 53, + "message": "The transaction's resources don't cover validation or the minimal transaction fee" + }, + "INSUFFICIENT_ACCOUNT_BALANCE": { + "code": 54, + "message": "Account balance is smaller than the transaction's max_fee" + }, + "VALIDATION_FAILURE": { + "code": 55, + "message": "Account validation failed", + "data": "string" + }, + "COMPILATION_FAILED": { + "code": 56, + "message": "Compilation failed" + }, + "CONTRACT_CLASS_SIZE_IS_TOO_LARGE": { + "code": 57, + "message": "Contract class size it too large" + }, + "NON_ACCOUNT": { + "code": 58, + "message": "Sender address in not an account contract" + }, + "DUPLICATE_TX": { + "code": 59, + "message": "A transaction with the same hash already exists in the mempool" + }, + "COMPILED_CLASS_HASH_MISMATCH": { + "code": 60, + "message": "the compiled class hash did not match the one supplied in the transaction" + }, + "UNSUPPORTED_TX_VERSION": { + "code": 61, + "message": "the transaction version is not supported" + }, + "UNSUPPORTED_CONTRACT_CLASS_VERSION": { + "code": 62, + "message": "the contract class version is not supported" + }, + "UNEXPECTED_ERROR": { + "code": 63, + "message": "An unexpected error occurred", + "data": "string" + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_ws_api.json b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_ws_api.json new file mode 100644 index 000000000..6887e1b7b --- /dev/null +++ b/crates/starknet-devnet-server/test_data/spec/0.8.0/starknet_ws_api.json @@ -0,0 +1,362 @@ +{ + "openrpc": "1.3.2", + "info": { + "version": "0.8.0-rc0", + "title": "StarkNet WebSocket PRC API", + "license": {} + }, + "methods": [ + { + "name": "starknet_subscribeNewHeads", + "summary": "New block headers subscription", + "description": "Creates a WebSocket stream which will fire events for new block headers", + "params": [ + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionNewHeads", + "summary": "New block headers notification", + "description": "Notification to the client of a new block header", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HEADER" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribeEvents", + "summary": "Events subscription", + "description": "Creates a WebSocket stream which will fire events for new Starknet events with applied filters", + "params": [ + { + "name": "from_address", + "summary": "Filter events by from_address which emitted the event", + "required": false, + "schema": { + "$ref": "#/components/schemas/ADDRESS" + } + }, + { + "name": "keys", + "summary": "The keys to filter events by", + "required": false, + "schema": { + "title": "event keys", + "$ref": "#/components/schemas/EVENT_KEYS" + } + }, + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "./api/starknet_api_openrpc.json#/components/errors/TOO_MANY_KEYS_IN_FILTER" + }, + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionEvents", + "summary": "New events notification", + "description": "Notification to the client of a new event", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/EMITTED_EVENT" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribeTransactionStatus", + "summary": "Transaction Status subscription", + "description": "Creates a WebSocket stream which will fire events when a transaction status is updated", + "params": [ + { + "name": "transaction_hash", + "summary": "The transaction hash to fetch status updates for", + "required": true, + "schema": { + "$ref": "#/components/schemas/FELT" + } + }, + { + "name": "block", + "summary": "The block to get notifications from, default is latest, limited to 1024 blocks back", + "required": false, + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_ID" + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_BLOCKS_BACK" + } + ] + }, + { + "name": "starknet_subscriptionTransactionsStatus", + "summary": "New transaction status notification", + "description": "Notification to the client of a new transaction status", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "schema": { + "$ref": "#/components/schemas/NEW_TXN_STATUS" + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscribePendingTransactions", + "summary": "New Pending Transactions subscription", + "description": "Creates a WebSocket stream which will fire events when a new pending transaction is added. While there is no mempool, this notifies of transactions in the pending block", + "params": [ + { + "name": "transaction_details", + "summary": "Get all transaction details, and not only the hash. If not provided, only hash is returned. Default is false", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "sender_address", + "summary": "Filter transactions to only receive notification from address list", + "required": false, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADDRESS" + } + } + } + ], + "result": { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + "errors": [ + { + "$ref": "#/components/errors/TOO_MANY_ADDRESSES_IN_FILTER" + } + ] + }, + { + "name": "starknet_subscriptionPendingTransactions", + "summary": "New pending transaction notification", + "description": "Notification to the client of a new pending transaction", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/SUBSCRIPTION_ID" + } + }, + { + "name": "result", + "description": "Either a tranasaction hash or full transaction details, based on subscription", + "schema": { + "oneOf": [ + { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN" + } + ] + } + } + ], + "errors": [] + }, + { + "name": "starknet_subscriptionReorg", + "description": "Notifies the subscriber of a reorganization of the chain", + "summary": "Can be received from subscribing to newHeads, Events, TransactionStatus", + "params": [ + { + "name": "subscription_id", + "schema": { + "$ref": "#/components/schemas/REORG_DATA" + } + } + ] + }, + { + "name": "starknet_unsubscribe", + "summary": "Closes a websocket subscription", + "description": "Close a previously opened ws stream, with the corresponding subscription id", + "params": [ + { + "name": "subscription_id", + "summary": "The subscription to close", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "result": { + "name": "Unsubscription result", + "description": "True if the unsubscription was successful", + "schema": { + "type": "boolean" + } + }, + "errors": [ + { + "$ref": "#/components/errors/INVALID_SUBSCRIPTION_ID" + } + ] + } + ], + "components": { + "schemas": { + "FELT": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/FELT" + }, + "ADDRESS": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/ADDRESS" + }, + "NEW_TXN_STATUS": { + "title": "New transaction Status", + "type": "object", + "properties": { + "transaction_hash": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_HASH" + }, + "status": { + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/TXN_STATUS_RESULT" + } + } + } + }, + "SUBSCRIPTION_ID": { + "name": "subscription id", + "description": "An identifier for this subscription stream used to associate events with this subscription.", + "schema": { + "type": "integer" + } + }, + "REORG_DATA": { + "name": "Reorg Data", + "description": "Data about reorganized blocks, starting and ending block number and hash", + "properties": { + "starting_block_hash": { + "title": "Starting Block Hash", + "description": "Hash of the first known block of the orphaned chain", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HASH" + } + }, + "starting_block_number": { + "title": "Starting Block Number", + "description": "Number of the first known block of the orphaned chain", + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_NUMBER" + }, + "ending_block_hash": { + "title": "Ending Block", + "description": "The last known block of the orphaned chain", + "schema": { + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_HASH" + } + }, + "ending_block_number": { + "title": "Ending Block Number", + "description": "Number of the last known block of the orphaned chain", + "$ref": "./api/starknet_api_openrpc.json#/components/schemas/BLOCK_NUMBER" + } + }, + "required": [ + "starting_block_hash", + "starting_block_number", + "ending_block_hash", + "ending_block_number" + ] + } + }, + "errors": { + "INVALID_SUBSCRIPTION_ID": { + "code": 66, + "message": "Invalid subscription id" + }, + "TOO_MANY_ADDRESSES_IN_FILTER": { + "code": 67, + "message": "Too many addresses in filter sender_address filter" + }, + "TOO_MANY_BLOCKS_BACK": { + "code": 68, + "message": "Cannot go back more than 1024 blocks" + } + } + } +} \ No newline at end of file diff --git a/crates/starknet-devnet-types/src/lib.rs b/crates/starknet-devnet-types/src/lib.rs index e2ccdf465..bf63b48d2 100644 --- a/crates/starknet-devnet-types/src/lib.rs +++ b/crates/starknet-devnet-types/src/lib.rs @@ -10,4 +10,5 @@ mod utils; // Re export libraries pub use rpc::{contract_address, contract_class, emitted_event, felt, messaging}; +pub use utils::{compile_sierra_contract, compile_sierra_contract_json}; pub use {num_bigint, starknet_api}; diff --git a/crates/starknet-devnet-types/src/rpc/block.rs b/crates/starknet-devnet-types/src/rpc/block.rs index 066c7d6fd..04523e0c9 100644 --- a/crates/starknet-devnet-types/src/rpc/block.rs +++ b/crates/starknet-devnet-types/src/rpc/block.rs @@ -118,3 +118,17 @@ pub struct ResourcePrice { pub price_in_fri: Felt, pub price_in_wei: Felt, } + +#[derive(Debug, Clone, Serialize)] +#[serde(deny_unknown_fields)] +/// Data about reorganized blocks, starting and ending block number and hash +pub struct ReorgData { + /// Hash of the first known block of the orphaned chain + pub starting_block_hash: BlockHash, + /// Number of the first known block of the orphaned chain + pub starting_block_number: BlockNumber, + /// The last known block of the orphaned chain + pub ending_block_hash: BlockHash, + /// Number of the last known block of the orphaned chain + pub ending_block_number: BlockNumber, +} diff --git a/crates/starknet-devnet-types/src/rpc/contract_class.rs b/crates/starknet-devnet-types/src/rpc/contract_class.rs index 6dad4b7ff..07d42364b 100644 --- a/crates/starknet-devnet-types/src/rpc/contract_class.rs +++ b/crates/starknet-devnet-types/src/rpc/contract_class.rs @@ -2,7 +2,6 @@ use core::fmt::Debug; use std::cmp::{Eq, PartialEq}; use blockifier::execution::contract_class::ClassInfo; -use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_lang_starknet_classes::contract_class::ContractClass as SierraContractClass; use serde::de::IntoDeserializer; use serde::{Serialize, Serializer}; @@ -12,9 +11,11 @@ use starknet_rs_core::types::{ }; use starknet_types_core::felt::Felt; +use crate::compile_sierra_contract_json; use crate::error::{ConversionError, DevnetResult, Error, JsonError}; use crate::serde_helpers::rpc_sierra_contract_class_to_sierra_contract_class::deserialize_to_sierra_contract_class; use crate::traits::HashProducer; +use crate::utils::compile_sierra_contract; pub mod deprecated; pub use deprecated::json_contract_class::Cairo0Json; @@ -113,14 +114,7 @@ impl TryFrom for blockifier::execution::contract_class::ContractC )) } ContractClass::Cairo1(sierra_contract_class) => { - let casm_json = - usc::compile_contract(serde_json::to_value(sierra_contract_class).map_err( - |err| Error::JsonError(JsonError::Custom { msg: err.to_string() }), - )?) - .map_err(|err| Error::SierraCompilationError { reason: err.to_string() })?; - - let casm = serde_json::from_value::(casm_json) - .map_err(|err| Error::JsonError(JsonError::Custom { msg: err.to_string() }))?; + let casm = compile_sierra_contract(&sierra_contract_class)?; let blockifier_contract_class: blockifier::execution::contract_class::ContractClassV1 = casm.try_into().map_err(|_| Error::ProgramError)?; @@ -253,13 +247,10 @@ pub fn convert_codegen_to_blockifier_compiled_class( class: CodegenContractClass, ) -> Result { Ok(match class { - CodegenContractClass::Sierra(_) => { - let json_value = serde_json::to_value(class).map_err(JsonError::SerdeJsonError)?; - let casm_json = usc::compile_contract(json_value) - .map_err(|err| Error::SierraCompilationError { reason: err.to_string() })?; + CodegenContractClass::Sierra(sierra) => { + let json_value = serde_json::to_value(sierra).map_err(JsonError::SerdeJsonError)?; - let casm = serde_json::from_value::(casm_json) - .map_err(|err| Error::JsonError(JsonError::Custom { msg: err.to_string() }))?; + let casm = compile_sierra_contract_json(json_value)?; let blockifier_contract_class: blockifier::execution::contract_class::ContractClassV1 = casm.try_into().map_err(|_| Error::ProgramError)?; diff --git a/crates/starknet-devnet-types/src/rpc/emitted_event.rs b/crates/starknet-devnet-types/src/rpc/emitted_event.rs index 454a46700..db3e8357f 100644 --- a/crates/starknet-devnet-types/src/rpc/emitted_event.rs +++ b/crates/starknet-devnet-types/src/rpc/emitted_event.rs @@ -41,3 +41,13 @@ impl From<&blockifier::execution::call_info::OrderedEvent> for OrderedEvent { } } } + +impl From<&EmittedEvent> for Event { + fn from(emitted_event: &EmittedEvent) -> Self { + Self { + from_address: emitted_event.from_address, + keys: emitted_event.keys.clone(), + data: emitted_event.data.clone(), + } + } +} diff --git a/crates/starknet-devnet-types/src/rpc/messaging.rs b/crates/starknet-devnet-types/src/rpc/messaging.rs index ba33bd496..5eeb08d1f 100644 --- a/crates/starknet-devnet-types/src/rpc/messaging.rs +++ b/crates/starknet-devnet-types/src/rpc/messaging.rs @@ -10,6 +10,7 @@ use crate::rpc::eth_address::EthAddressWrapper; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct MessageToL2 { + pub l1_transaction_hash: Option, pub l2_contract_address: ContractAddress, pub entry_point_selector: EntryPointSelector, pub l1_contract_address: ContractAddress, diff --git a/crates/starknet-devnet-types/src/rpc/transaction_receipt.rs b/crates/starknet-devnet-types/src/rpc/transaction_receipt.rs index 227ff1ef9..e4207ba96 100644 --- a/crates/starknet-devnet-types/src/rpc/transaction_receipt.rs +++ b/crates/starknet-devnet-types/src/rpc/transaction_receipt.rs @@ -18,6 +18,19 @@ pub enum TransactionReceipt { Common(CommonTransactionReceipt), } +impl TransactionReceipt { + pub fn get_block_number(&self) -> Option { + match self { + TransactionReceipt::Deploy(receipt) => &receipt.common, + TransactionReceipt::L1Handler(receipt) => &receipt.common, + TransactionReceipt::Common(receipt) => receipt, + } + .maybe_pending_properties + .block_number + .map(|BlockNumber(n)| n) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct DeployTransactionReceipt { #[serde(flatten)] diff --git a/crates/starknet-devnet-types/src/rpc/transactions.rs b/crates/starknet-devnet-types/src/rpc/transactions.rs index bde9c9da8..1880fb3af 100644 --- a/crates/starknet-devnet-types/src/rpc/transactions.rs +++ b/crates/starknet-devnet-types/src/rpc/transactions.rs @@ -20,7 +20,7 @@ use starknet_api::transaction::{Fee, Resource, Tip}; use starknet_rs_core::crypto::compute_hash_on_elements; use starknet_rs_core::types::{ BlockId, ExecutionResult, Felt, ResourceBounds, ResourceBoundsMapping, - TransactionFinalityStatus, + TransactionExecutionStatus, TransactionFinalityStatus, }; use starknet_rs_crypto::poseidon_hash_many; @@ -164,6 +164,16 @@ impl TransactionWithHash { execution_resources, } } + + pub fn get_sender_address(&self) -> Option { + match &self.transaction { + Transaction::Declare(tx) => Some(tx.get_sender_address()), + Transaction::DeployAccount(tx) => Some(*tx.get_contract_address()), + Transaction::Deploy(_) => None, + Transaction::Invoke(tx) => Some(tx.get_sender_address()), + Transaction::L1Handler(tx) => Some(tx.contract_address), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -173,6 +183,14 @@ pub struct TransactionWithReceipt { pub transaction: Transaction, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionStatus { + pub finality_status: TransactionFinalityStatus, + pub failure_reason: Option, + pub execution_status: TransactionExecutionStatus, +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum DeclareTransaction { @@ -181,6 +199,16 @@ pub enum DeclareTransaction { V3(DeclareTransactionV3), } +impl DeclareTransaction { + pub fn get_sender_address(&self) -> ContractAddress { + match self { + DeclareTransaction::V1(tx) => tx.sender_address, + DeclareTransaction::V2(tx) => tx.sender_address, + DeclareTransaction::V3(tx) => tx.sender_address, + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum InvokeTransaction { @@ -188,6 +216,15 @@ pub enum InvokeTransaction { V3(InvokeTransactionV3), } +impl InvokeTransaction { + pub fn get_sender_address(&self) -> ContractAddress { + match self { + InvokeTransaction::V1(tx) => tx.sender_address, + InvokeTransaction::V3(tx) => tx.sender_address, + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum DeployAccountTransaction { @@ -1109,6 +1146,14 @@ impl FunctionInvocation { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct L1HandlerTransactionStatus { + pub transaction_hash: TransactionHash, + pub finality_status: TransactionFinalityStatus, + pub failure_reason: Option, +} + #[cfg(test)] mod tests { use starknet_rs_crypto::poseidon_hash_many; diff --git a/crates/starknet-devnet-types/src/rpc/transactions/declare_transaction_v3.rs b/crates/starknet-devnet-types/src/rpc/transactions/declare_transaction_v3.rs index 8f46ed59c..a6d6f0567 100644 --- a/crates/starknet-devnet-types/src/rpc/transactions/declare_transaction_v3.rs +++ b/crates/starknet-devnet-types/src/rpc/transactions/declare_transaction_v3.rs @@ -19,7 +19,7 @@ pub struct DeclareTransactionV3 { paymaster_data: Vec, nonce_data_availability_mode: DataAvailabilityMode, fee_data_availability_mode: DataAvailabilityMode, - sender_address: ContractAddress, + pub(crate) sender_address: ContractAddress, compiled_class_hash: CompiledClassHash, class_hash: ClassHash, account_deployment_data: Vec, diff --git a/crates/starknet-devnet-types/src/rpc/transactions/invoke_transaction_v3.rs b/crates/starknet-devnet-types/src/rpc/transactions/invoke_transaction_v3.rs index 682cf69e2..b2b988f62 100644 --- a/crates/starknet-devnet-types/src/rpc/transactions/invoke_transaction_v3.rs +++ b/crates/starknet-devnet-types/src/rpc/transactions/invoke_transaction_v3.rs @@ -20,7 +20,7 @@ pub struct InvokeTransactionV3 { nonce_data_availability_mode: DataAvailabilityMode, fee_data_availability_mode: DataAvailabilityMode, account_deployment_data: Vec, - sender_address: ContractAddress, + pub(crate) sender_address: ContractAddress, calldata: Calldata, } diff --git a/crates/starknet-devnet-types/src/rpc/transactions/l1_handler_transaction.rs b/crates/starknet-devnet-types/src/rpc/transactions/l1_handler_transaction.rs index 4fe79d833..272d57c00 100644 --- a/crates/starknet-devnet-types/src/rpc/transactions/l1_handler_transaction.rs +++ b/crates/starknet-devnet-types/src/rpc/transactions/l1_handler_transaction.rs @@ -11,7 +11,7 @@ use starknet_api::transaction::{ TransactionHash as ApiTransactionHash, TransactionVersion as ApiTransactionVersion, }; use starknet_rs_core::crypto::compute_hash_on_elements; -use starknet_rs_core::types::Felt; +use starknet_rs_core::types::{Felt, Hash256}; use super::{deserialize_paid_fee_on_l1, serialize_paid_fee_on_l1}; use crate::constants::PREFIX_L1_HANDLER; @@ -23,6 +23,9 @@ use crate::rpc::messaging::MessageToL2; #[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct L1HandlerTransaction { + /// The hash of the L1 transaction that triggered this L1 handler execution. + /// Omissible if received via mock (devnet_postmanSendMessageToL2) + pub l1_transaction_hash: Option, pub version: TransactionVersion, pub nonce: Nonce, pub contract_address: ContractAddress, @@ -102,9 +105,8 @@ impl L1HandlerTransaction { calldata, nonce: message.nonce, paid_fee_on_l1, - // Currently, only version 0 is supported, which - // is ensured by default initialization. - ..Default::default() + l1_transaction_hash: message.l1_transaction_hash, + version: Felt::ZERO, // currently, only version 0 is supported }) } } @@ -122,6 +124,7 @@ impl TryFrom<&L1HandlerTransaction> for MessageToL2 { let payload = value.calldata[1..].to_vec(); Ok(MessageToL2 { + l1_transaction_hash: value.l1_transaction_hash, l2_contract_address: value.contract_address, entry_point_selector: value.entry_point_selector, l1_contract_address: ContractAddress::new(*l1_contract_address)?, @@ -158,6 +161,7 @@ mod tests { vec![felt_from_prefixed_hex(from_address).unwrap(), Felt::ONE, Felt::TWO]; let message = MessageToL2 { + l1_transaction_hash: None, l1_contract_address: ContractAddress::new( felt_from_prefixed_hex(from_address).unwrap(), ) @@ -170,8 +174,6 @@ mod tests { paid_fee_on_l1: fee.into(), }; - let chain_id = ChainId::goerli_legacy_id(); - let transaction_hash = felt_from_prefixed_hex( "0x6182c63599a9638272f1ce5b5cadabece9c81c2d2b8f88ab7a294472b8fce8b", ) @@ -199,6 +201,6 @@ mod tests { let transaction = L1HandlerTransaction::try_from_message_to_l2(message).unwrap(); assert_eq!(transaction, expected_tx); - assert_eq!(transaction.compute_hash(chain_id), transaction_hash); + assert_eq!(transaction.compute_hash(ChainId::goerli_legacy_id()), transaction_hash); } } diff --git a/crates/starknet-devnet-types/src/utils.rs b/crates/starknet-devnet-types/src/utils.rs index eab4eba12..bdf4450a5 100644 --- a/crates/starknet-devnet-types/src/utils.rs +++ b/crates/starknet-devnet-types/src/utils.rs @@ -1,8 +1,12 @@ use std::io; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use cairo_lang_starknet_classes::contract_class::ContractClass; use serde_json::ser::Formatter; use serde_json::{Map, Value}; +use crate::error::{DevnetResult, Error, JsonError}; + /// The preserve_order feature enabled in the serde_json crate /// removing a key from the object changes the order of the keys /// When serde_json is not being used with the preserver order feature @@ -83,6 +87,23 @@ impl Formatter for StarknetFormatter { } } +pub fn compile_sierra_contract(sierra_contract: &ContractClass) -> DevnetResult { + let sierra_contract_json = serde_json::to_value(sierra_contract) + .map_err(|err| Error::JsonError(JsonError::SerdeJsonError(err)))?; + + compile_sierra_contract_json(sierra_contract_json) +} + +pub fn compile_sierra_contract_json( + sierra_contract_json: serde_json::Value, +) -> DevnetResult { + let casm_json = usc::compile_contract(sierra_contract_json) + .map_err(|err| Error::SierraCompilationError { reason: err.to_string() })?; + + serde_json::from_value::(casm_json) + .map_err(|err| Error::JsonError(JsonError::SerdeJsonError(err))) +} + #[cfg(test)] pub(crate) mod test_utils { use starknet_api::data_availability::DataAvailabilityMode; diff --git a/crates/starknet-devnet/Cargo.toml b/crates/starknet-devnet/Cargo.toml index 81663f804..f3b8889fe 100644 --- a/crates/starknet-devnet/Cargo.toml +++ b/crates/starknet-devnet/Cargo.toml @@ -36,6 +36,7 @@ serde_json = { workspace = true } serde = { workspace = true } anyhow = { workspace = true } starknet-rs-providers = { workspace = true } +reqwest = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/starknet-devnet/src/main.rs b/crates/starknet-devnet/src/main.rs index 9ac1c48c4..f2c38eb3b 100644 --- a/crates/starknet-devnet/src/main.rs +++ b/crates/starknet-devnet/src/main.rs @@ -7,11 +7,11 @@ use clap::Parser; use cli::Args; use futures::future::join_all; use serde::de::IntoDeserializer; +use serde_json::json; use server::api::http::HttpApiHandler; use server::api::json_rpc::{JsonRpcHandler, RPC_SPEC_VERSION}; use server::api::Api; -use server::dump_util::{dump_events, load_events, DumpEvent}; -use server::rpc_core::request::{Id, RequestParams, Version}; +use server::dump_util::{dump_events, load_events}; use server::server::serve_http_api_json_rpc; use starknet_core::account::Account; use starknet_core::constants::{ @@ -38,7 +38,7 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::windows::ctrl_c; use tokio::task::{self}; use tokio::time::{interval, sleep}; -use tracing::{info, warn}; +use tracing::{error, info, warn}; use tracing_subscriber::EnvFilter; mod cli; @@ -321,7 +321,8 @@ async fn main() -> Result<(), anyhow::Error> { if let BlockGenerationOn::Interval(seconds) = starknet_config.block_generation_on { // use JoinHandle to run block interval creation as a task - let block_interval_handle = task::spawn(create_block_interval(api.clone(), seconds)); + let full_address = format!("http://{address}"); + let block_interval_handle = task::spawn(create_block_interval(seconds, full_address)); tasks.push(block_interval_handle); } @@ -347,8 +348,8 @@ async fn main() -> Result<(), anyhow::Error> { #[allow(clippy::expect_used)] async fn create_block_interval( - api: Api, block_interval_seconds: u64, + devnet_address: String, ) -> Result<(), std::io::Error> { #[cfg(unix)] let mut sigint = { signal(SignalKind::interrupt()).expect("Failed to setup SIGINT handler") }; @@ -359,21 +360,21 @@ async fn create_block_interval( Box::pin(ctrl_c_signal) }; + let devnet_client = reqwest::Client::new(); + let block_req_body = json!({ "jsonrpc": "2.0", "id": 0, "method": "devnet_createBlock" }); + + // avoid creating block instantly after startup + sleep(Duration::from_secs(block_interval_seconds)).await; + let mut interval = interval(Duration::from_secs(block_interval_seconds)); loop { - // TODO does this need to be inside of the loop? or outside? - // avoid creating block instantly after startup - sleep(Duration::from_secs(block_interval_seconds)).await; - tokio::select! { _ = interval.tick() => { - let mut starknet = api.starknet.lock().await; - let mut dumpable_events = api.dumpable_events.lock().await; - info!("Generating block on time interval"); - - // manually add event for dumping; alternative: create a client and send request - starknet.create_block().map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - dumpable_events.push(DumpEvent { jsonrpc: Version::V2, method: "devnet_createBlock".into(), params: RequestParams::None, id: Id::Number(0) }); + // By sending a request, we take care of: 1) dumping 2) notifying subscribers + match devnet_client.post(&devnet_address).json(&block_req_body).send().await { + Ok(_) => info!("Generating block on time interval"), + Err(e) => error!("Failed block creation on time interval: {e:?}") + } } _ = sigint.recv() => { return Ok(()) diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 8c7e8789d..d4c3edc0d 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -17,6 +17,7 @@ async-trait = { workspace = true } axum = { workspace = true } anyhow = { workspace = true } ethers = { workspace = true } +futures = { workspace = true } lazy_static = { workspace = true } listeners = { workspace = true } rand = { workspace = true } @@ -30,5 +31,6 @@ starknet-rs-providers = { workspace = true } starknet-rs-signers = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["signal"] } +tokio-tungstenite = { workspace = true } url = { workspace = true } usc = { workspace = true } diff --git a/tests/integration/common/background_devnet.rs b/tests/integration/common/background_devnet.rs index 3abb97f4b..24318af46 100644 --- a/tests/integration/common/background_devnet.rs +++ b/tests/integration/common/background_devnet.rs @@ -18,7 +18,7 @@ use starknet_rs_signers::{LocalWallet, SigningKey}; use url::Url; use super::constants::{ - ACCOUNTS, HEALTHCHECK_PATH, HOST, PREDEPLOYED_ACCOUNT_INITIAL_BALANCE, RPC_PATH, SEED, + ACCOUNTS, HEALTHCHECK_PATH, HOST, PREDEPLOYED_ACCOUNT_INITIAL_BALANCE, RPC_PATH, SEED, WS_PATH, }; use super::errors::{RpcError, TestError}; use super::reqwest_client::{PostReqwestSender, ReqwestClient}; @@ -183,6 +183,10 @@ impl BackgroundDevnet { }) } + pub fn ws_url(&self) -> String { + format!("ws://{HOST}:{}{WS_PATH}", self.port) + } + pub async fn send_custom_rpc( &self, method: &str, @@ -387,6 +391,30 @@ impl BackgroundDevnet { } } + pub async fn abort_blocks( + &self, + starting_block_id: &BlockId, + ) -> Result, anyhow::Error> { + let mut aborted_blocks = self + .send_custom_rpc( + "devnet_abortBlocks", + json!({ "starting_block_id" : starting_block_id }), + ) + .await + .map_err(|e| anyhow::Error::msg(e.to_string()))?; + + let aborted_blocks = aborted_blocks["aborted"] + .take() + .as_array() + .ok_or(anyhow::Error::msg("Invalid abort response"))? + .clone(); + + Ok(aborted_blocks + .into_iter() + .map(|block_hash| serde_json::from_value(block_hash).unwrap()) + .collect()) + } + pub async fn get_config(&self) -> serde_json::Value { self.send_custom_rpc("devnet_getConfig", json!({})).await.unwrap() } diff --git a/tests/integration/common/constants.rs b/tests/integration/common/constants.rs index d269a7cf8..13af0e395 100644 --- a/tests/integration/common/constants.rs +++ b/tests/integration/common/constants.rs @@ -14,6 +14,7 @@ pub const DEVNET_EXECUTABLE_BINARY_PATH: &str = // URL paths pub const RPC_PATH: &str = "/rpc"; pub const HEALTHCHECK_PATH: &str = "/is_alive"; +pub const WS_PATH: &str = "/ws"; // predeployed account info with seed=42 pub const PREDEPLOYED_ACCOUNT_ADDRESS: &str = diff --git a/tests/integration/common/errors.rs b/tests/integration/common/errors.rs index 03825a743..8a7687e55 100644 --- a/tests/integration/common/errors.rs +++ b/tests/integration/common/errors.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::borrow::Cow; use serde::{self, Deserialize, Serialize}; @@ -58,3 +59,9 @@ pub struct RpcError { #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } + +impl fmt::Display for RpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} diff --git a/tests/integration/common/utils.rs b/tests/integration/common/utils.rs index 762745d6c..027148ae1 100644 --- a/tests/integration/common/utils.rs +++ b/tests/integration/common/utils.rs @@ -3,9 +3,12 @@ use std::fs; use std::path::Path; use std::process::{Child, Command}; use std::sync::Arc; +use std::time::Duration; use ethers::types::U256; +use futures::{SinkExt, StreamExt, TryStreamExt}; use rand::{thread_rng, Rng}; +use serde_json::json; use server::test_utils::assert_contains; use starknet_rs_accounts::{ Account, AccountFactory, ArgentAccountFactory, OpenZeppelinAccountFactory, SingleOwnerAccount, @@ -17,9 +20,13 @@ use starknet_rs_core::types::{ Felt, FlattenedSierraClass, FunctionCall, NonZeroFelt, }; use starknet_rs_core::utils::{get_selector_from_name, get_udc_deployed_address}; -use starknet_rs_providers::jsonrpc::HttpTransport; -use starknet_rs_providers::{JsonRpcClient, Provider}; +use starknet_rs_providers::jsonrpc::{ + HttpTransport, HttpTransportError, JsonRpcClientError, JsonRpcError, +}; +use starknet_rs_providers::{JsonRpcClient, Provider, ProviderError}; use starknet_rs_signers::LocalWallet; +use tokio::net::TcpStream; +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use super::background_devnet::BackgroundDevnet; use super::constants::{ @@ -234,6 +241,31 @@ impl Drop for UniqueAutoDeletableFile { } } +/// Deploys an instance of the class whose sierra hash is provided as `class_hash`. Uses a v1 invoke +/// transaction. Returns the address of the newly deployed contract. +pub async fn deploy_v1( + account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, + class_hash: Felt, + ctor_args: &[Felt], +) -> Result { + let contract_factory = ContractFactory::new(class_hash, account); + contract_factory + .deploy_v1(ctor_args.to_vec(), Felt::ZERO, false) + .max_fee(Felt::from(1e18 as u128)) + .send() + .await?; + + // generate the address of the newly deployed contract + let contract_address = get_udc_deployed_address( + Felt::ZERO, + class_hash, + &starknet_rs_core::utils::UdcUniqueness::NotUnique, + ctor_args, + ); + + Ok(contract_address) +} + /// Declares and deploys a Cairo 1 contract; returns class hash and contract address pub async fn declare_v3_deploy_v3( account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, @@ -332,6 +364,129 @@ pub fn get_gas_units_and_gas_price(fee_estimate: FeeEstimate) -> (u64, u128) { (gas_units.to_le_digits().first().cloned().unwrap(), gas_price) } +/// Helper for extracting JSON RPC error from the provider instance of `ProviderError`. +/// To be used when there are discrepancies between starknet-rs and the target RPC spec. +pub fn extract_json_rpc_error(error: ProviderError) -> Result { + match error { + ProviderError::Other(provider_impl_error) => { + let impl_specific_error: &JsonRpcClientError = + provider_impl_error.as_any().downcast_ref().unwrap(); + match impl_specific_error { + JsonRpcClientError::JsonRpcError(json_rpc_error) => Ok(json_rpc_error.clone()), + other => { + Err(anyhow::Error::msg(format!("Cannot extract RPC error from: {:?}", other))) + } + } + } + other => Err(anyhow::Error::msg(format!("Cannot extract RPC error from: {:?}", other))), + } +} + +pub fn assert_json_rpc_errors_equal(e1: JsonRpcError, e2: JsonRpcError) { + assert_eq!((e1.code, e1.message, e1.data), (e2.code, e2.message, e2.data)); +} + +pub async fn send_text_rpc_via_ws( + ws: &mut WebSocketStream>, + method: &str, + params: serde_json::Value, +) -> Result { + let text_body = json!({ + "jsonrpc": "2.0", + "id": 0, + "method": method, + "params": params, + }) + .to_string(); + ws.send(tokio_tungstenite::tungstenite::Message::Text(text_body)).await?; + + let resp_raw = + ws.next().await.ok_or(anyhow::Error::msg("No response in websocket stream"))??; + let resp_body: serde_json::Value = serde_json::from_slice(&resp_raw.into_data())?; + + Ok(resp_body) +} + +pub async fn send_binary_rpc_via_ws( + ws: &mut WebSocketStream>, + method: &str, + params: serde_json::Value, +) -> Result { + let body = json!({ + "jsonrpc": "2.0", + "id": 0, + "method": method, + "params": params, + }); + let binary_body = serde_json::to_vec(&body)?; + ws.send(tokio_tungstenite::tungstenite::Message::Binary(binary_body)).await?; + + let resp_raw = + ws.next().await.ok_or(anyhow::Error::msg("No response in websocket stream"))??; + let resp_body: serde_json::Value = serde_json::from_slice(&resp_raw.into_data())?; + + Ok(resp_body) +} + +pub type SubscriptionId = u64; + +pub async fn subscribe( + ws: &mut WebSocketStream>, + subscription_method: &str, + params: serde_json::Value, +) -> Result { + let subscription_confirmation = send_text_rpc_via_ws(ws, subscription_method, params).await?; + subscription_confirmation["result"].as_u64().ok_or(anyhow::Error::msg(format!( + "No ID in subscription response: {subscription_confirmation}" + ))) +} + +/// Tries to read from the provided ws stream. To prevent deadlock, waits for a second at most. +pub async fn receive_rpc_via_ws( + ws: &mut WebSocketStream>, +) -> Result { + let msg = tokio::time::timeout(Duration::from_secs(1), ws.try_next()) + .await?? + .ok_or(anyhow::Error::msg("Nothing to read"))?; + Ok(serde_json::from_str(&msg.into_text()?)?) +} + +/// Extract `result` from the notification and assert general properties +pub async fn receive_notification( + ws: &mut WebSocketStream>, + method: &str, + expected_subscription_id: SubscriptionId, +) -> Result { + let mut notification = receive_rpc_via_ws(ws).await?; + assert_eq!(notification["jsonrpc"], "2.0"); + assert_eq!(notification["method"], method); + assert_eq!(notification["params"]["subscription_id"], expected_subscription_id); + Ok(notification["params"]["result"].take()) +} + +pub async fn assert_no_notifications(ws: &mut WebSocketStream>) { + match receive_rpc_via_ws(ws).await { + Ok(resp) => panic!("Expected no notifications; found: {resp}"), + Err(e) if e.to_string().contains("deadline has elapsed") => { /* expected */ } + Err(e) => panic!("Expected to error out due to empty channel; found: {e}"), + } +} + +pub async fn subscribe_new_heads( + ws: &mut WebSocketStream>, + block_specifier: serde_json::Value, +) -> Result { + subscribe(ws, "starknet_subscribeNewHeads", block_specifier).await +} + +pub async fn unsubscribe( + ws: &mut WebSocketStream>, + subscription_id: SubscriptionId, +) -> Result { + send_text_rpc_via_ws(ws, "starknet_unsubscribe", json!({ "subscription_id": subscription_id })) + .await +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum FeeUnit { diff --git a/tests/integration/general_rpc_tests.rs b/tests/integration/general_rpc_tests.rs index e3ee8cacc..918cbdb9e 100644 --- a/tests/integration/general_rpc_tests.rs +++ b/tests/integration/general_rpc_tests.rs @@ -1,4 +1,5 @@ use serde_json::json; +use starknet_rs_core::types::BlockId; use crate::common::background_devnet::BackgroundDevnet; use crate::common::constants::RPC_PATH; @@ -50,3 +51,23 @@ async fn rpc_returns_invalid_params() { assert_eq!(rpc_error.code, -32602); } + +#[tokio::test] +async fn storage_proof_request_should_always_return_error() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + devnet.create_block().await.unwrap(); + + for (req_params, expected_code, expected_msg) in [ + (json!({}), -32602, "missing field `block_id`"), + (json!({ "block_id": BlockId::Number(0) }), 42, "Devnet doesn't support storage proofs"), + (json!({ "block_id": "latest" }), 42, "Devnet doesn't support storage proofs"), + (json!({ "block_id": BlockId::Number(5) }), 24, "Block not found"), + ] { + let error = + devnet.send_custom_rpc("starknet_getStorageProof", req_params).await.unwrap_err(); + assert_eq!( + error, + RpcError { code: expected_code.into(), message: expected_msg.into(), data: None } + ); + } +} diff --git a/tests/integration/get_transaction_receipt_by_hash.rs b/tests/integration/get_transaction_receipt_by_hash.rs index ba5c36be4..9db318aa6 100644 --- a/tests/integration/get_transaction_receipt_by_hash.rs +++ b/tests/integration/get_transaction_receipt_by_hash.rs @@ -51,9 +51,7 @@ async fn deploy_account_transaction_receipt() { TransactionReceipt::DeployAccount(receipt) => { assert_eq!(receipt.contract_address, new_account_address); } - _ => { - panic!("Invalid receipt {:?}", deploy_account_receipt); - } + _ => panic!("Invalid receipt {:?}", deploy_account_receipt), } } @@ -242,9 +240,7 @@ async fn declare_v1_transaction_fails_with_insufficient_max_fee() { match declare_transaction_result { Err(ProviderError::StarknetError(StarknetError::InsufficientMaxFee)) => (), - _ => { - panic!("Invalid result: {:?}", declare_transaction_result); - } + _ => panic!("Invalid result: {:?}", declare_transaction_result), } } diff --git a/tests/integration/lib.rs b/tests/integration/lib.rs index c4b753746..689994e74 100644 --- a/tests/integration/lib.rs +++ b/tests/integration/lib.rs @@ -29,6 +29,13 @@ mod test_old_state; mod test_restart; mod test_restrictive_mode; mod test_simulate_transactions; +mod test_subscription_to_blocks; +mod test_subscription_to_events; +mod test_subscription_to_pending_txs; +mod test_subscription_to_reorg; +mod test_subscription_to_tx_status; +mod test_subscription_with_invalid_block_id; mod test_trace; mod test_transaction_handling; mod test_v3_transactions; +mod test_websocket; diff --git a/tests/integration/test_abort_blocks.rs b/tests/integration/test_abort_blocks.rs index 28d42f161..015e0fd54 100644 --- a/tests/integration/test_abort_blocks.rs +++ b/tests/integration/test_abort_blocks.rs @@ -9,37 +9,13 @@ use crate::common::utils::{assert_tx_reverted, to_hex_felt, FeeUnit}; static DUMMY_ADDRESS: u128 = 1; static DUMMY_AMOUNT: u128 = 1; -async fn abort_blocks(devnet: &BackgroundDevnet, starting_block_id: &BlockId) -> Vec { - let mut aborted_blocks = devnet - .send_custom_rpc( - "devnet_abortBlocks", - json!({ - "starting_block_id" : starting_block_id - }), - ) - .await - .unwrap(); - - let aborted_blocks = aborted_blocks["aborted"].take().as_array().unwrap().clone(); - - aborted_blocks - .into_iter() - .map(|block_hash| serde_json::from_value(block_hash).unwrap()) - .collect() -} - async fn abort_blocks_error( devnet: &BackgroundDevnet, starting_block_id: &BlockId, expected_message_substring: &str, ) { let aborted_blocks_error = devnet - .send_custom_rpc( - "devnet_abortBlocks", - json!({ - "starting_block_id" : starting_block_id - }), - ) + .send_custom_rpc("devnet_abortBlocks", json!({ "starting_block_id" : starting_block_id })) .await .unwrap_err(); assert_contains(&aborted_blocks_error.message, expected_message_substring); @@ -68,7 +44,7 @@ async fn abort_latest_block_with_hash() { let genesis_block_hash = devnet.get_latest_block_with_tx_hashes().await.unwrap().block_hash; let new_block_hash = devnet.create_block().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(new_block_hash)).await; + let aborted_blocks = devnet.abort_blocks(&BlockId::Hash(new_block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![new_block_hash]); // Check if the genesis block still has ACCEPTED_ON_L2 status @@ -103,7 +79,7 @@ async fn abort_two_blocks() { let first_block_hash = devnet.create_block().await.unwrap(); let second_block_hash = devnet.create_block().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(first_block_hash)).await; + let aborted_blocks = devnet.abort_blocks(&BlockId::Hash(first_block_hash)).await.unwrap(); assert_eq!(json!(aborted_blocks), json!([second_block_hash, first_block_hash])); assert_block_rejected(&devnet, &first_block_hash).await; @@ -121,7 +97,8 @@ async fn abort_block_with_transaction() { let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(latest_block.block_hash)).await; + let aborted_blocks = + devnet.abort_blocks(&BlockId::Hash(latest_block.block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![latest_block.block_hash]); assert_tx_reverted(&mint_hash, &devnet.json_rpc_client, &["Block aborted manually"]).await; @@ -135,7 +112,7 @@ async fn query_aborted_block_by_number_should_fail() { .expect("Could not start Devnet"); let new_block_hash = devnet.create_block().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(new_block_hash)).await; + let aborted_blocks = devnet.abort_blocks(&BlockId::Hash(new_block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![new_block_hash]); assert_block_rejected(&devnet, &new_block_hash).await; @@ -164,14 +141,15 @@ async fn abort_block_state_revert() { devnet.mint(DUMMY_ADDRESS, DUMMY_AMOUNT).await; let second_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(second_block.block_hash)).await; + let aborted_blocks = + devnet.abort_blocks(&BlockId::Hash(second_block.block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![second_block.block_hash]); let balance = devnet.get_balance_latest(&Felt::from(DUMMY_ADDRESS), FeeUnit::Wei).await.unwrap(); assert_eq!(balance.to_string(), DUMMY_AMOUNT.to_string()); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(first_block.block_hash)).await; + let aborted_blocks = devnet.abort_blocks(&BlockId::Hash(first_block.block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![first_block.block_hash]); let balance = @@ -211,7 +189,7 @@ async fn abort_same_block_twice() { let first_block_hash = devnet.create_block().await.unwrap(); let second_block_hash = devnet.create_block().await.unwrap(); - let aborted_blocks = abort_blocks(&devnet, &BlockId::Hash(first_block_hash)).await; + let aborted_blocks = devnet.abort_blocks(&BlockId::Hash(first_block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![second_block_hash, first_block_hash]); abort_blocks_error(&devnet, &BlockId::Hash(first_block_hash), "Block is already aborted").await; @@ -230,7 +208,7 @@ async fn abort_block_after_fork() { let fork_block_hash = fork_devnet.create_block().await.unwrap(); - let aborted_blocks = abort_blocks(&fork_devnet, &BlockId::Hash(fork_block_hash)).await; + let aborted_blocks = fork_devnet.abort_blocks(&BlockId::Hash(fork_block_hash)).await.unwrap(); assert_eq!(aborted_blocks, vec![fork_block_hash]); abort_blocks_error(&fork_devnet, &BlockId::Hash(fork_block_hash), "Block is already aborted") @@ -248,7 +226,7 @@ async fn abort_latest_blocks() { devnet.create_block().await.unwrap(); } for _ in 0..3 { - abort_blocks(&devnet, &BlockId::Tag(BlockTag::Latest)).await; + devnet.abort_blocks(&BlockId::Tag(BlockTag::Latest)).await.unwrap(); } abort_blocks_error(&devnet, &BlockId::Tag(BlockTag::Latest), "Genesis block can't be aborted") .await; @@ -273,7 +251,7 @@ async fn abort_pending_block() { .unwrap(); assert_eq!(pending_balance, (2 * DUMMY_AMOUNT).into()); - abort_blocks(&devnet, &BlockId::Tag(BlockTag::Pending)).await; + devnet.abort_blocks(&BlockId::Tag(BlockTag::Pending)).await.unwrap(); let latest_balance = devnet.get_balance_latest(&Felt::from(DUMMY_ADDRESS), FeeUnit::Wei).await.unwrap(); assert_eq!(latest_balance, DUMMY_AMOUNT.into()); diff --git a/tests/integration/test_call.rs b/tests/integration/test_call.rs index 2ece78594..69f891fc9 100644 --- a/tests/integration/test_call.rs +++ b/tests/integration/test_call.rs @@ -1,16 +1,25 @@ +use starknet_rs_accounts::SingleOwnerAccount; use starknet_rs_core::types::{BlockId, BlockTag, Felt, FunctionCall, StarknetError}; +use starknet_rs_core::utils::{cairo_short_string_to_felt, get_selector_from_name}; +use starknet_rs_providers::jsonrpc::JsonRpcError; use starknet_rs_providers::{Provider, ProviderError}; use crate::common::background_devnet::BackgroundDevnet; -use crate::common::constants::{ETH_ERC20_CONTRACT_ADDRESS, PREDEPLOYED_ACCOUNT_ADDRESS}; +use crate::common::constants::{ + CAIRO_1_ERC20_CONTRACT_CLASS_HASH, CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH, + ETH_ERC20_CONTRACT_ADDRESS, PREDEPLOYED_ACCOUNT_ADDRESS, +}; +use crate::common::utils::{ + assert_json_rpc_errors_equal, declare_v3_deploy_v3, deploy_v1, extract_json_rpc_error, + get_flattened_sierra_contract_and_casm_hash, +}; #[tokio::test] /// This test doesn't rely on devnet.get_balance because it's not supposed to call ERC20 async fn calling_method_of_undeployed_contract() { let devnet = BackgroundDevnet::spawn().await.expect("Could not start Devnet"); let contract_address = Felt::from_hex_unchecked(PREDEPLOYED_ACCOUNT_ADDRESS); - let entry_point_selector = - starknet_rs_core::utils::get_selector_from_name("balanceOf").unwrap(); + let entry_point_selector = get_selector_from_name("balanceOf").unwrap(); let undeployed_address = Felt::from_hex_unchecked("0x1234"); let err = devnet @@ -36,8 +45,7 @@ async fn calling_method_of_undeployed_contract() { async fn calling_nonexistent_contract_method() { let devnet = BackgroundDevnet::spawn().await.expect("Could not start Devnet"); let contract_address = Felt::from_hex_unchecked(PREDEPLOYED_ACCOUNT_ADDRESS); - let entry_point_selector = - starknet_rs_core::utils::get_selector_from_name("nonExistentMethod").unwrap(); + let entry_point_selector = get_selector_from_name("nonExistentMethod").unwrap(); let err = devnet .json_rpc_client @@ -52,8 +60,73 @@ async fn calling_nonexistent_contract_method() { .await .expect_err("Should have failed"); - match err { - ProviderError::StarknetError(StarknetError::ContractError(_)) => (), - _ => panic!("Invalid error: {err:?}"), - } + let selector_hex = entry_point_selector.to_hex_string(); + assert_json_rpc_errors_equal( + extract_json_rpc_error(err).unwrap(), + JsonRpcError { + code: 40, + message: "Contract error".into(), + data: Some(serde_json::json!({ + "contract_address": ETH_ERC20_CONTRACT_ADDRESS.to_hex_string(), + "class_hash": CAIRO_1_ERC20_CONTRACT_CLASS_HASH, + "selector": selector_hex, + "error": format!("Entry point EntryPointSelector({selector_hex}) not found in contract.\n") + })), + }, + ); +} + +#[tokio::test] +async fn call_panicking_method() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (signer, account_address) = devnet.get_first_predeployed_account().await; + let account = SingleOwnerAccount::new( + &devnet.json_rpc_client, + signer, + account_address, + devnet.json_rpc_client.chain_id().await.unwrap(), + starknet_rs_accounts::ExecutionEncoding::New, + ); + + let (contract_class, casm_hash) = + get_flattened_sierra_contract_and_casm_hash(CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH); + + let (class_hash, contract_address) = + declare_v3_deploy_v3(&account, contract_class, casm_hash, &[]).await.unwrap(); + let other_contract_address = deploy_v1(&account, class_hash, &[]).await.unwrap(); + + let top_selector = get_selector_from_name("create_panic_in_another_contract").unwrap(); + let panic_message = cairo_short_string_to_felt("funny_text").unwrap(); + let err = devnet + .json_rpc_client + .call( + FunctionCall { + contract_address, + entry_point_selector: top_selector, + calldata: vec![other_contract_address, panic_message], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await + .unwrap_err(); + + assert_json_rpc_errors_equal( + extract_json_rpc_error(err).unwrap(), + JsonRpcError { + code: 40, + message: "Contract error".into(), + data: Some(serde_json::json!({ + "contract_address": contract_address, + "class_hash": class_hash, + "selector": top_selector, + "error": { + "contract_address": other_contract_address, + "class_hash": class_hash, + "selector": get_selector_from_name("create_panic").unwrap(), + "error": "Execution failed. Failure reason: 0x66756e6e795f74657874 ('funny_text').\n" + } + })), + }, + ); } diff --git a/tests/integration/test_estimate_fee.rs b/tests/integration/test_estimate_fee.rs index eac7c7a46..9f896e0e1 100644 --- a/tests/integration/test_estimate_fee.rs +++ b/tests/integration/test_estimate_fee.rs @@ -12,23 +12,24 @@ use starknet_rs_core::types::{ BlockId, BlockTag, BroadcastedDeclareTransactionV1, BroadcastedDeclareTransactionV3, BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV1, BroadcastedInvokeTransactionV3, BroadcastedTransaction, Call, DataAvailabilityMode, FeeEstimate, Felt, FunctionCall, - ResourceBounds, ResourceBoundsMapping, SimulationFlagForEstimateFee, StarknetError, - TransactionExecutionErrorData, + ResourceBounds, ResourceBoundsMapping, StarknetError, TransactionExecutionErrorData, }; use starknet_rs_core::utils::{ cairo_short_string_to_felt, get_selector_from_name, get_udc_deployed_address, UdcUniqueness, }; -use starknet_rs_providers::{Provider, ProviderError}; -use starknet_rs_signers::Signer; +use starknet_rs_providers::jsonrpc::{HttpTransport, JsonRpcError}; +use starknet_rs_providers::{JsonRpcClient, Provider, ProviderError}; +use starknet_rs_signers::{LocalWallet, Signer}; use crate::common::background_devnet::BackgroundDevnet; use crate::common::constants::{ CAIRO_0_ACCOUNT_CONTRACT_HASH, CAIRO_1_CONTRACT_PATH, CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH, - CAIRO_1_VERSION_ASSERTER_SIERRA_PATH, CHAIN_ID, QUERY_VERSION_OFFSET, UDC_CONTRACT_ADDRESS, + CAIRO_1_VERSION_ASSERTER_SIERRA_PATH, CHAIN_ID, ETH_ERC20_CONTRACT_ADDRESS, + QUERY_VERSION_OFFSET, UDC_CONTRACT_ADDRESS, }; use crate::common::utils::{ - assert_tx_reverted, assert_tx_successful, get_deployable_account_signer, - get_flattened_sierra_contract_and_casm_hash, + assert_json_rpc_errors_equal, assert_tx_reverted, assert_tx_successful, extract_json_rpc_error, + get_deployable_account_signer, get_flattened_sierra_contract_and_casm_hash, }; fn assert_fee_estimation(fee_estimation: &FeeEstimate) { @@ -110,29 +111,36 @@ async fn estimate_fee_of_invalid_deploy_account() { let devnet = BackgroundDevnet::spawn().await.expect("Could not start Devnet"); let new_account_signer = get_deployable_account_signer(); - let dummy_invalid_class_hash = Felt::from_hex_unchecked("0x123"); + let invalid_class_hash = Felt::from_hex_unchecked("0x123"); let account_factory = OpenZeppelinAccountFactory::new( - dummy_invalid_class_hash, + invalid_class_hash, CHAIN_ID, new_account_signer, devnet.clone_provider(), ) .await .unwrap(); - let new_account_nonce = Felt::ZERO; let salt = Felt::from_hex_unchecked("0x123"); - let err = account_factory - .deploy_v1(salt) - .nonce(new_account_nonce) - .estimate_fee() - .await - .expect_err("Should have failed"); - match err { - AccountFactoryError::Provider(ProviderError::StarknetError( - StarknetError::TransactionExecutionError(_), - )) => (), - _ => panic!("Invalid error: {err:?}"), + let deployment = account_factory.deploy_v1(salt); + match deployment.estimate_fee().await { + Err(AccountFactoryError::Provider(provider_error)) => assert_json_rpc_errors_equal( + extract_json_rpc_error(provider_error).unwrap(), + JsonRpcError { + code: 41, + message: "Transaction execution error".into(), + data: Some(serde_json::json!({ + "transaction_index": 0, + "execution_error": { + "contract_address": deployment.address(), + "class_hash": invalid_class_hash, + "selector": null, + "error": format!("Class with hash {} is not declared.\n", invalid_class_hash.to_fixed_hex_string()) + } + })), + }, + ), + other => panic!("Unexpected response: {other:?}"), } } @@ -408,10 +416,14 @@ async fn message_available_if_estimation_reverts() { match invoke_err { AccountError::Provider(ProviderError::StarknetError( StarknetError::TransactionExecutionError(TransactionExecutionErrorData { + transaction_index, execution_error, .. }), - )) => assert_contains(&execution_error, panic_reason), + )) => { + assert_eq!(transaction_index, 0); + assert_contains(&execution_error, panic_reason); + } other => panic!("Invalid err: {other:?}"), }; } @@ -469,6 +481,35 @@ async fn using_query_version_if_estimating() { } } +async fn broadcasted_invoke_v1_for_estimation( + account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, + signer: &LocalWallet, + to_address: Felt, + selector: Felt, + calldata: &[Felt], + nonce: Felt, +) -> Result { + let calls = vec![Call { to: to_address, selector, calldata: calldata.to_vec() }]; + let calldata = account.encode_calls(&calls); + + let max_fee = Felt::ZERO; + let prepared_invoke = account.execute_v1(calls).nonce(nonce).max_fee(max_fee).prepared()?; + + let is_query = false; + let signature = signer.sign_hash(&prepared_invoke.transaction_hash(is_query)).await?; + + Ok(BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V1( + BroadcastedInvokeTransactionV1 { + max_fee, + signature: vec![signature.r, signature.s], + nonce, + sender_address: account.address(), + calldata, + is_query, + }, + ))) +} + #[tokio::test] /// estimate fee of declare + deploy (invoke udc) async fn estimate_fee_of_multiple_txs() { @@ -492,11 +533,13 @@ async fn estimate_fee_of_multiple_txs() { let contract_class: Arc = Arc::new(serde_json::from_value(contract_json.inner).unwrap()); + let declaration_nonce = Felt::ZERO; + let declaration_max_fee = Felt::ZERO; let class_hash = contract_class.class_hash().unwrap(); let prepared_legacy_declaration = account .declare_legacy(contract_class.clone()) - .max_fee(Felt::ZERO) - .nonce(Felt::ZERO) + .max_fee(declaration_max_fee) + .nonce(declaration_nonce) .prepared() .unwrap(); @@ -506,25 +549,6 @@ async fn estimate_fee_of_multiple_txs() { .await .unwrap(); - let calls = vec![Call { - to: UDC_CONTRACT_ADDRESS, - selector: get_selector_from_name("deployContract").unwrap(), - calldata: vec![ - class_hash, - Felt::from_hex_unchecked("0x123"), // salt - Felt::ZERO, - Felt::ZERO, - ], - }]; - - let calldata = account.encode_calls(&calls); - - let prepared_invoke = - account.execute_v1(calls).nonce(Felt::ONE).max_fee(Felt::ZERO).prepared().unwrap(); - - let deployment_signature = - signer.sign_hash(&prepared_invoke.transaction_hash(query_only)).await.unwrap(); - devnet .json_rpc_client .estimate_fee( @@ -532,26 +556,30 @@ async fn estimate_fee_of_multiple_txs() { BroadcastedTransaction::Declare( starknet_rs_core::types::BroadcastedDeclareTransaction::V1( BroadcastedDeclareTransactionV1 { - max_fee: Felt::ZERO, - signature: [declaration_signature.r, declaration_signature.s].to_vec(), - nonce: Felt::ZERO, + max_fee: declaration_max_fee, + signature: vec![declaration_signature.r, declaration_signature.s], + nonce: declaration_nonce, sender_address: account_address, contract_class: contract_class.compress().unwrap().into(), is_query: query_only, }, ), ), - BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V1( - BroadcastedInvokeTransactionV1 { - max_fee: Felt::ZERO, - // precalculated signature - signature: [deployment_signature.r, deployment_signature.s].to_vec(), - nonce: Felt::ONE, - sender_address: account_address, - calldata, - is_query: query_only, - }, - )), + broadcasted_invoke_v1_for_estimation( + &account, + &signer, + UDC_CONTRACT_ADDRESS, + get_selector_from_name("deployContract").unwrap(), + &[ + class_hash, + Felt::from_hex_unchecked("0x123"), // salt + Felt::ZERO, + Felt::ZERO, + ], + Felt::ONE, + ) + .await + .unwrap(), ], [], // simulation_flags BlockId::Tag(BlockTag::Latest), @@ -563,102 +591,69 @@ async fn estimate_fee_of_multiple_txs() { } #[tokio::test] -async fn estimate_fee_of_declare_and_deploy_via_udc_returns_index_of_second_transaction_when_executed_with_non_existing_method() - { - let devnet = BackgroundDevnet::spawn().await.expect("Could not start devnet"); - - // get account +async fn estimate_fee_of_multiple_txs_with_second_failing() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); let (signer, account_address) = devnet.get_first_predeployed_account().await; - let mut account = SingleOwnerAccount::new( + let account = SingleOwnerAccount::new( &devnet.json_rpc_client, signer.clone(), account_address, - devnet.json_rpc_client.chain_id().await.unwrap(), + CHAIN_ID, ExecutionEncoding::New, ); - account.set_block_id(BlockId::Tag(BlockTag::Latest)); - - let (flattened_contract_artifact, casm_hash) = - get_flattened_sierra_contract_and_casm_hash(CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH); - let class_hash = flattened_contract_artifact.class_hash(); - - let estimate_fee_resource_bounds = ResourceBoundsMapping { - l1_gas: ResourceBounds { max_amount: 0, max_price_per_unit: 0 }, - l2_gas: ResourceBounds { max_amount: 0, max_price_per_unit: 0 }, - }; + let non_existent_selector = get_selector_from_name("nonExistentMethod").unwrap(); - // call non existent method in UDC - let calls = vec![Call { - to: UDC_CONTRACT_ADDRESS, - selector: get_selector_from_name("no_such_method").unwrap(), - calldata: vec![ - class_hash, - Felt::from_hex_unchecked("0x123"), // salt - Felt::ZERO, - Felt::ZERO, - ], - }]; - - let calldata = account.encode_calls(&calls); - - let is_query = true; - let nonce_data_availability_mode = DataAvailabilityMode::L1; - let fee_data_availability_mode = DataAvailabilityMode::L1; - - let expected_error = devnet + let err = devnet .json_rpc_client .estimate_fee( [ - BroadcastedTransaction::Declare( - starknet_rs_core::types::BroadcastedDeclareTransaction::V3( - BroadcastedDeclareTransactionV3 { - sender_address: account_address, - compiled_class_hash: casm_hash, - signature: vec![], - nonce: Felt::ZERO, - contract_class: Arc::new(flattened_contract_artifact.clone()), - resource_bounds: estimate_fee_resource_bounds.clone(), - tip: 0, - paymaster_data: vec![], - account_deployment_data: vec![], - nonce_data_availability_mode, - fee_data_availability_mode, - is_query, - }, - ), - ), - BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3( - BroadcastedInvokeTransactionV3 { - sender_address: account_address, - calldata, - signature: vec![], - nonce: Felt::ONE, - resource_bounds: estimate_fee_resource_bounds, - tip: 0, - paymaster_data: vec![], - account_deployment_data: vec![], - nonce_data_availability_mode, - fee_data_availability_mode, - is_query, - }, - )), + broadcasted_invoke_v1_for_estimation( + &account, + &signer, + ETH_ERC20_CONTRACT_ADDRESS, + get_selector_from_name("transfer").unwrap(), + &[ + Felt::ONE, // recipient + Felt::from(1_000_000_000), // low part of uint256 + Felt::ZERO, // high part of uint256 + ], + Felt::ZERO, // original nonce + ) + .await + .unwrap(), + broadcasted_invoke_v1_for_estimation( + &account, + &signer, + ETH_ERC20_CONTRACT_ADDRESS, + non_existent_selector, + &[], + Felt::ONE, // nonce incremented after 1st tx + ) + .await + .unwrap(), ], - [SimulationFlagForEstimateFee::SkipValidate], - account.block_id(), + [], // simulation_flags + BlockId::Tag(BlockTag::Latest), ) .await .unwrap_err(); - match expected_error { + match err { ProviderError::StarknetError(StarknetError::TransactionExecutionError( TransactionExecutionErrorData { transaction_index, execution_error }, )) => { assert_eq!(transaction_index, 1); - assert_contains(&execution_error, "not found in contract"); + assert_contains( + &execution_error, + &format!( + "Entry point EntryPointSelector({}) not found in contract", + non_existent_selector.to_hex_string() + ), + ); } - other => panic!("Unexpected error: {:?}", other), - } + _ => panic!("Unexpected error: {err}"), + }; } #[tokio::test] diff --git a/tests/integration/test_fork.rs b/tests/integration/test_fork.rs index 635735fb0..7f4580a0c 100644 --- a/tests/integration/test_fork.rs +++ b/tests/integration/test_fork.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use std::sync::Arc; -use server::test_utils::assert_contains; use starknet_rs_accounts::{ Account, AccountFactory, AccountFactoryError, ExecutionEncoding, OpenZeppelinAccountFactory, SingleOwnerAccount, @@ -15,6 +14,7 @@ use starknet_rs_core::types::{ use starknet_rs_core::utils::{ get_selector_from_name, get_storage_var_address, get_udc_deployed_address, }; +use starknet_rs_providers::jsonrpc::JsonRpcError; use starknet_rs_providers::{Provider, ProviderError}; use starknet_rs_signers::Signer; @@ -25,7 +25,8 @@ use crate::common::constants::{ MAINNET_URL, }; use crate::common::utils::{ - assert_cairo1_classes_equal, assert_tx_successful, declare_v3_deploy_v3, + assert_cairo1_classes_equal, assert_json_rpc_errors_equal, assert_tx_successful, + declare_v3_deploy_v3, extract_json_rpc_error, get_block_reader_contract_in_sierra_and_compiled_class_hash, get_contract_balance, get_simple_contract_in_sierra_and_compiled_class_hash, send_ctrl_c_signal_and_wait, FeeUnit, }; @@ -472,7 +473,20 @@ async fn test_fork_if_origin_dies() { let address = Felt::ONE; match fork_devnet.json_rpc_client.get_nonce(BlockId::Tag(BlockTag::Latest), address).await { - Err(ProviderError::Other(e)) => assert_contains(&e.to_string(), "error sending request"), + Err(provider_error) => { + assert_json_rpc_errors_equal( + extract_json_rpc_error(provider_error).unwrap(), + JsonRpcError { + code: -1, + message: format!( + "Failed to read from state: Error in communication with forking origin: \ + error sending request for url ({}/).", + origin_devnet.url + ), + data: None, + }, + ); + } unexpected => panic!("Got unexpected resp: {unexpected:?}"), } } diff --git a/tests/integration/test_get_class.rs b/tests/integration/test_get_class.rs index 9da009901..e26f502f2 100644 --- a/tests/integration/test_get_class.rs +++ b/tests/integration/test_get_class.rs @@ -1,15 +1,18 @@ use std::sync::Arc; +use starknet_core::CasmContractClass; use starknet_rs_accounts::{Account, ExecutionEncoding, SingleOwnerAccount}; use starknet_rs_core::chain_id; use starknet_rs_core::types::contract::legacy::LegacyContractClass; use starknet_rs_core::types::{BlockId, BlockTag, ContractClass, Felt, StarknetError}; +use starknet_rs_providers::jsonrpc::JsonRpcError; use starknet_rs_providers::{Provider, ProviderError}; use crate::common::background_devnet::BackgroundDevnet; -use crate::common::constants::PREDEPLOYED_ACCOUNT_ADDRESS; +use crate::common::constants::{CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH, PREDEPLOYED_ACCOUNT_ADDRESS}; use crate::common::utils::{ assert_cairo1_classes_equal, get_events_contract_in_sierra_and_compiled_class_hash, + get_flattened_sierra_contract_and_casm_hash, }; #[tokio::test] @@ -222,13 +225,7 @@ async fn test_getting_class_after_block_abortion() { let abortable_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); - devnet - .send_custom_rpc( - "devnet_abortBlocks", - serde_json::json!({ "starting_block_id": BlockId::Hash(abortable_block.block_hash) }), - ) - .await - .unwrap(); + devnet.abort_blocks(&BlockId::Hash(abortable_block.block_hash)).await.unwrap(); // Getting class at the following block IDs should NOT be successful after abortion; these // blocks exist, but their states don't contain the class. @@ -266,3 +263,69 @@ async fn test_getting_class_after_block_abortion() { } } } + +#[tokio::test] +async fn test_getting_compiled_casm_for_cairo0_or_non_existing_hash_have_to_return_class_hash_not_found_error() + { + let devnet = BackgroundDevnet::spawn_with_additional_args(&["--account-class", "cairo0"]) + .await + .expect("Could not start Devnet"); + + let (_, account_address) = devnet.get_first_predeployed_account().await; + + let block_id = BlockId::Tag(BlockTag::Latest); + + let class_hash = + devnet.json_rpc_client.get_class_hash_at(block_id, account_address).await.unwrap(); + + // Felt::ONE is non existing class hash + for el in [class_hash, Felt::ONE] { + match get_compiled_casm(&devnet, block_id, el).await.unwrap_err() { + StarknetError::ClassHashNotFound => {} + other => panic!("Unexpected error {:?}", other), + } + } +} + +#[tokio::test] +async fn test_getting_compiled_casm_for_cairo_1_have_to_succeed() { + let devnet = BackgroundDevnet::spawn_with_additional_args(&["--account-class", "cairo1"]) + .await + .expect("Could not start Devnet"); + + let (_, expected_casm_hash) = + get_flattened_sierra_contract_and_casm_hash(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH); + + let (_, account_address) = devnet.get_first_predeployed_account().await; + + let block_id = BlockId::Tag(BlockTag::Latest); + + let class_hash = + devnet.json_rpc_client.get_class_hash_at(block_id, account_address).await.unwrap(); + + let casm = get_compiled_casm(&devnet, block_id, class_hash).await.unwrap(); + assert_eq!(casm.compiled_class_hash(), expected_casm_hash); +} + +async fn get_compiled_casm( + devnet: &BackgroundDevnet, + block_id: BlockId, + class_hash: Felt, +) -> Result { + devnet + .send_custom_rpc( + "starknet_getCompiledCasm", + serde_json::json!({ + "block_id": block_id, + "class_hash": format!("{:#x}", class_hash), + }), + ) + .await + .map(|json_value| serde_json::from_value::(json_value).unwrap()) + .map_err(|err| { + let json_rpc_error = + JsonRpcError { code: err.code, message: err.message.to_string(), data: err.data }; + + (&json_rpc_error).try_into().unwrap() + }) +} diff --git a/tests/integration/test_messaging.rs b/tests/integration/test_messaging.rs index a9d20569a..5083fa32b 100644 --- a/tests/integration/test_messaging.rs +++ b/tests/integration/test_messaging.rs @@ -743,3 +743,114 @@ async fn flushing_only_new_messages_after_restart() { devnet.send_custom_rpc("devnet_postmanFlush", json!({})).await.unwrap(); assert_eq!(get_balance(&devnet, sn_l1l2_contract, user_sn).await, [Felt::ONE]); } + +#[tokio::test] +async fn test_getting_status_of_mock_message() { + let (devnet, _, l1l2_contract_address) = setup_devnet(&[]).await; + + // Use postman to send a message to l2 without l1 - the message increments user balance + let increment_amount = Felt::from(0xff); + + let user = Felt::ONE; + let l1_tx_hash = Felt::from(0xabc); + let mock_msg_body = json!({ + "l1_contract_address": MESSAGING_L1_ADDRESS, + "l2_contract_address": l1l2_contract_address, + "entry_point_selector": get_selector_from_name("deposit").unwrap(), + "payload": [user, increment_amount], + "paid_fee_on_l1": "0x1234", + "nonce": "0x1", + "l1_transaction_hash": l1_tx_hash, + }); + + let mock_msg_resp = + devnet.send_custom_rpc("devnet_postmanSendMessageToL2", mock_msg_body).await.unwrap(); + assert_eq!(get_balance(&devnet, l1l2_contract_address, user).await, [increment_amount]); + + let messages_status = devnet + .send_custom_rpc("starknet_getMessagesStatus", json!({ "transaction_hash": l1_tx_hash })) + .await + .unwrap(); + assert_eq!( + messages_status, + json!([{ + "transaction_hash": mock_msg_resp["transaction_hash"], + "finality_status": "ACCEPTED_ON_L2", + "failure_reason": null, + }]) + ); +} + +#[tokio::test] +async fn test_getting_status_of_real_message() { + let anvil = BackgroundAnvil::spawn().await.unwrap(); + let (devnet, sn_account, sn_l1l2_contract) = setup_devnet(&[]).await; + + // Load l1 messaging contract. + let body: serde_json::Value = devnet + .send_custom_rpc("devnet_postmanLoad", json!({ "network_url": anvil.url })) + .await + .expect("deploy l1 messaging contract failed"); + + assert_eq!( + body.get("messaging_contract_address").unwrap().as_str().unwrap(), + MESSAGING_L1_ADDRESS + ); + + // Deploy the L1L2 testing contract on L1 (on L2 it's already pre-deployed). + let l1_messaging_address = H160::from_str(MESSAGING_L1_ADDRESS).unwrap(); + let eth_l1l2_address = anvil.deploy_l1l2_contract(l1_messaging_address).await.unwrap(); + + let eth_l1l2_address_hex = format!("{eth_l1l2_address:#x}"); + let eth_l1l2_address_felt = Felt::from_hex_unchecked(ð_l1l2_address_hex); + + // Set balance to 1 for the user 1 on L2 and withdraw to L1. + let user_sn = Felt::ONE; + let user_balance = Felt::ONE; + increase_balance(sn_account.clone(), sn_l1l2_contract, user_sn, user_balance).await; + withdraw(sn_account, sn_l1l2_contract, user_sn, user_balance, eth_l1l2_address_felt).await; + + // Flush to send the messages. + devnet.send_custom_rpc("devnet_postmanFlush", json!({})).await.unwrap(); + + let user_eth = 1.into(); + let sn_l1l2_contract_u256 = felt_to_u256(sn_l1l2_contract); + + // Consume the message to increase the balance on L1 + anvil.withdraw_l1l2(eth_l1l2_address, sn_l1l2_contract_u256, user_eth, 1.into()).await.unwrap(); + + // Send back the amount 1 to the user 1 on L2. + anvil.deposit_l1l2(eth_l1l2_address, sn_l1l2_contract_u256, user_eth, 1.into()).await.unwrap(); + + // Flush to trigger L2 transaction generation. + let generated_l2_txs_raw = + &devnet.send_custom_rpc("devnet_postmanFlush", json!({})).await.unwrap() + ["generated_l2_transactions"]; + let generated_l2_txs = generated_l2_txs_raw.as_array().unwrap(); + assert_eq!(generated_l2_txs.len(), 1); + let generated_l2_tx = &generated_l2_txs[0]; + + let latest_l1_txs = anvil + .provider + .get_block(ethers::types::BlockId::Number(ethers::types::BlockNumber::Latest)) + .await + .unwrap() + .unwrap() + .transactions; + + assert_eq!(latest_l1_txs.len(), 1); + let latest_l1_tx = latest_l1_txs[0]; + + let messages_status = devnet + .send_custom_rpc("starknet_getMessagesStatus", json!({ "transaction_hash": latest_l1_tx })) + .await + .unwrap(); + assert_eq!( + messages_status, + json!([{ + "transaction_hash": generated_l2_tx, + "finality_status": "ACCEPTED_ON_L2", + "failure_reason": null, + }]) + ) +} diff --git a/tests/integration/test_simulate_transactions.rs b/tests/integration/test_simulate_transactions.rs index 43a249642..2d890d5a2 100644 --- a/tests/integration/test_simulate_transactions.rs +++ b/tests/integration/test_simulate_transactions.rs @@ -34,9 +34,8 @@ use crate::common::constants::{ use crate::common::fees::{assert_difference_if_validation, assert_fee_in_resp_at_least_equal}; use crate::common::utils::{ declare_v3_deploy_v3, get_deployable_account_signer, - get_flattened_sierra_contract_and_casm_hash, get_gas_units_and_gas_price, - get_simple_contract_in_sierra_and_compiled_class_hash, iter_to_hex_felt, to_hex_felt, - to_num_as_hex, + get_flattened_sierra_contract_and_casm_hash, get_gas_units_and_gas_price, iter_to_hex_felt, + to_hex_felt, to_num_as_hex, }; #[tokio::test] @@ -437,12 +436,12 @@ async fn using_query_version_if_simulating() { "transactions": [ { "type": "INVOKE", - "max_fee": max_fee.to_hex_string(), + "max_fee": max_fee, "version": "0x1", - "signature": iter_to_hex_felt(&[signature.r, signature.s]), - "nonce": to_num_as_hex(&nonce), - "calldata": iter_to_hex_felt(&account.encode_calls(&calls)), - "sender_address": account.address().to_hex_string(), + "signature": [signature.r, signature.s], + "nonce": nonce, + "calldata": account.encode_calls(&calls), + "sender_address": account.address(), } ] }); @@ -450,6 +449,53 @@ async fn using_query_version_if_simulating() { devnet.send_custom_rpc("starknet_simulateTransactions", invoke_simulation_body).await.unwrap(); } +#[tokio::test] +async fn test_simulation_of_panicking_invoke() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (signer, account_address) = devnet.get_first_predeployed_account().await; + let account = SingleOwnerAccount::new( + &devnet.json_rpc_client, + signer.clone(), + account_address, + devnet.json_rpc_client.chain_id().await.unwrap(), + starknet_rs_accounts::ExecutionEncoding::New, + ); + + let (contract_class, casm_hash) = + get_flattened_sierra_contract_and_casm_hash(CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH); + + let (_, contract_address) = + declare_v3_deploy_v3(&account, contract_class, casm_hash, &[]).await.unwrap(); + + let top_selector = get_selector_from_name("create_panic").unwrap(); + let panic_message_text = "funny_text"; + let panic_message = cairo_short_string_to_felt(panic_message_text).unwrap(); + + let calls = + vec![Call { to: contract_address, selector: top_selector, calldata: vec![panic_message] }]; + + let max_fee = Felt::from(1e18 as u128); + let nonce = Felt::TWO; // after declare + deploy + let simulation = account + .execute_v1(calls) + .max_fee(max_fee) + .nonce(nonce) + .simulate(false, false) + .await + .unwrap(); + + match simulation.transaction_trace { + TransactionTrace::Invoke(InvokeTransactionTrace { + execute_invocation: ExecuteInvocation::Reverted(reverted_invocation), + .. + }) => { + assert_contains(&reverted_invocation.revert_reason, panic_message_text); + } + other_trace => panic!("Unexpected trace {other_trace:?}"), + } +} + #[tokio::test] async fn simulate_of_multiple_txs_shouldnt_return_an_error_if_invoke_transaction_reverts() { let devnet = BackgroundDevnet::spawn().await.expect("Could not start devnet"); @@ -672,12 +718,7 @@ async fn simulate_with_max_fee_exceeding_account_balance_returns_error_if_fee_ch execution_error, .. }), - )) => { - assert_contains( - &execution_error, - "Account balance is not enough to cover the transaction cost.", - ); - } + )) => assert_contains(&execution_error, "exceed balance"), other => panic!("Unexpected error {other:?}"), } @@ -1014,7 +1055,8 @@ async fn simulate_invoke_declare_deploy_account_with_either_gas_or_gas_price_set )) => { assert_eq!( execution_error, - "Provided max fee is not enough to cover the transaction cost." + "The transaction's resources don't cover validation or the minimal \ + transaction fee." ); } other => panic!("Unexpected error: {:?}", other), @@ -1072,56 +1114,3 @@ async fn simulate_invoke_v3_with_failing_execution_should_return_a_trace_of_reve other => panic!("Unexpected trace {other:?}"), } } - -/// Test with lower than (estimated_gas_units * gas_price) using two flags. With -/// skip_fee_transfer shouldnt fail, without it should fail. -#[tokio::test] -async fn simulate_declare_v3_with_less_than_estimated_fee_should_revert_if_fee_charge_is_not_skipped() - { - let devnet = BackgroundDevnet::spawn().await.expect("Could not start Devnet"); - let (sierra_artifact, casm_hash) = get_simple_contract_in_sierra_and_compiled_class_hash(); - - let (signer, account_address) = devnet.get_first_predeployed_account().await; - - let mut account = SingleOwnerAccount::new( - &devnet.json_rpc_client, - signer, - account_address, - constants::CHAIN_ID, - ExecutionEncoding::New, - ); - account.set_block_id(BlockId::Tag(BlockTag::Latest)); - - let fee_estimate = account - .declare_v3(Arc::new(sierra_artifact.clone()), casm_hash) - .estimate_fee() - .await - .unwrap(); - - let (gas_units, gas_price) = get_gas_units_and_gas_price(fee_estimate); - - for skip_fee_charge in [true, false] { - let simulation_result = account - .declare_v3(Arc::new(sierra_artifact.clone()), casm_hash) - .gas(gas_units) - .gas_price(gas_price - 1) - .simulate(false, skip_fee_charge) - .await; - - match (simulation_result, skip_fee_charge) { - (Ok(_), true) => {} - ( - Err(AccountError::Provider(ProviderError::StarknetError( - StarknetError::TransactionExecutionError(TransactionExecutionErrorData { - execution_error, - .. - }), - ))), - false, - ) => { - assert_contains(&execution_error, "max fee is not enough"); - } - invalid_combination => panic!("Invalid combination: {invalid_combination:?}"), - } - } -} diff --git a/tests/integration/test_subscription_to_blocks.rs b/tests/integration/test_subscription_to_blocks.rs new file mode 100644 index 000000000..1f493505b --- /dev/null +++ b/tests/integration/test_subscription_to_blocks.rs @@ -0,0 +1,260 @@ +use std::collections::HashMap; +use std::time::Duration; + +use serde_json::json; +use starknet_core::constants::ETH_ERC20_CONTRACT_ADDRESS; +use starknet_rs_core::types::{BlockId, BlockTag}; +use starknet_rs_providers::Provider; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::utils::{ + assert_no_notifications, receive_notification, subscribe_new_heads, unsubscribe, SubscriptionId, +}; + +async fn receive_block( + ws: &mut WebSocketStream>, + subscription_id: SubscriptionId, +) -> Result { + receive_notification(ws, "starknet_subscriptionNewHeads", subscription_id).await +} + +#[tokio::test] +async fn subscribe_to_new_block_heads_happy_path() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_id = subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + + // test with multiple blocks created, number 0 was origin, so we start at 1 + for block_i in 1..=2 { + let created_block_hash = devnet.create_block().await.unwrap(); + + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_hash"], json!(created_block_hash)); + assert_eq!(notification_block["block_number"], json!(block_i)); + } +} + +#[tokio::test] +async fn should_not_receive_block_notification_if_not_subscribed() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.create_block().await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn multiple_block_subscribers_happy_path() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let n_subscribers = 5; + + let mut subscribers = HashMap::new(); + for _ in 0..n_subscribers { + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + subscribers.insert(subscription_id, ws); + } + + assert_eq!(subscribers.len(), n_subscribers); // assert all IDs are different + + let created_block_hash = devnet.create_block().await.unwrap(); + + for (subscription_id, mut ws) in subscribers { + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_hash"], json!(created_block_hash)); + assert_eq!(notification_block["block_number"], json!(1)); + } +} + +#[tokio::test] +async fn subscription_to_an_old_block_by_number_should_notify_of_all_blocks_up_to_latest() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let n_blocks = 5; + for _ in 0..n_blocks { + devnet.create_block().await.unwrap(); + } + + // request notifications for all blocks starting with genesis + let subscription_id = + subscribe_new_heads(&mut ws, json!({ "block_id": BlockId::Number(0) })).await.unwrap(); + + for block_i in 0..=n_blocks { + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_number"], json!(block_i)); + } + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn subscription_to_an_old_block_by_hash_should_notify_of_all_blocks_up_to_latest() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let genesis_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + let n_blocks = 5; + for _ in 0..n_blocks { + devnet.create_block().await.unwrap(); + } + + // request notifications for all blocks starting with genesis + let subscription_id = + subscribe_new_heads(&mut ws, json!({ "block_id": BlockId::Hash(genesis_block.block_hash)})) + .await + .unwrap(); + + let starting_block = 0; + for block_i in starting_block..=n_blocks { + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_number"], json!(block_i)); + } + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn assert_latest_block_is_default() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws_latest, _) = connect_async(devnet.ws_url()).await.unwrap(); + let (mut ws_default, _) = connect_async(devnet.ws_url()).await.unwrap(); + + // create two subscriptions: one to latest, one without block (thus defaulting) + let subscription_id_latest = + subscribe_new_heads(&mut ws_latest, json!({ "block_id": "latest" })).await.unwrap(); + + let subscription_id_default = subscribe_new_heads(&mut ws_default, json!({})).await.unwrap(); + + assert_ne!(subscription_id_latest, subscription_id_default); + + devnet.create_block().await.unwrap(); + + let notification_block_latest = + receive_block(&mut ws_latest, subscription_id_latest).await.unwrap(); + assert_no_notifications(&mut ws_latest).await; + + let notification_block_default = + receive_block(&mut ws_default, subscription_id_default).await.unwrap(); + assert_no_notifications(&mut ws_default).await; + + assert_eq!(notification_block_latest, notification_block_default); +} + +#[tokio::test] +async fn test_multiple_subscribers_one_unsubscribes() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let n_subscribers = 3; + + let mut subscribers = HashMap::new(); + for _ in 0..n_subscribers { + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + subscribers.insert(subscription_id, ws); + } + + assert_eq!(subscribers.len(), n_subscribers); // assert all IDs are different + + // randomly choose one subscriber for unsubscription + let unsubscriber_id = *subscribers.keys().next().expect("Should have at least one"); + + // unsubscribe + let mut unsubscriber_ws = subscribers.remove(&unsubscriber_id).unwrap(); + let unsubscription_resp = unsubscribe(&mut unsubscriber_ws, unsubscriber_id).await.unwrap(); + assert_eq!(unsubscription_resp, json!({ "jsonrpc": "2.0", "id": 0, "result": true })); + + // create block and assert only subscribers are notified + let created_block_hash = devnet.create_block().await.unwrap(); + + for (subscription_id, mut ws) in subscribers { + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_hash"], json!(created_block_hash)); + } + + assert_no_notifications(&mut unsubscriber_ws).await; +} + +#[tokio::test] +async fn test_unsubscribing_invalid_id() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let dummy_id = 123; + let unsubscription_resp = unsubscribe(&mut ws, dummy_id).await.unwrap(); + + assert_eq!( + unsubscription_resp, + json!({ + "jsonrpc": "2.0", + "id": 0, + "error": { + "code": 66, + "message": "Invalid subscription id", + } + }) + ); +} + +#[tokio::test] +async fn read_only_methods_do_not_generate_notifications() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + + devnet + .json_rpc_client + .get_class_hash_at(BlockId::Tag(BlockTag::Latest), ETH_ERC20_CONTRACT_ADDRESS) + .await + .unwrap(); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn test_notifications_in_block_on_demand_mode() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + + let dummy_address = 0x1; + devnet.mint(dummy_address, 1).await; + + assert_no_notifications(&mut ws).await; + + let created_block_hash = devnet.create_block().await.unwrap(); + + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_hash"], json!(created_block_hash)); + assert_eq!(notification_block["block_number"], json!(1)); +} + +#[tokio::test] +async fn test_notifications_on_periodic_block_generation() { + let interval = 3; + let devnet_args = ["--block-generation-on", &interval.to_string()]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + + // this assertion is skipped due to CI instability + // assert_no_notifications(&mut ws).await; + + // should be enough time for Devnet to mine a single new block + tokio::time::sleep(Duration::from_secs(interval + 1)).await; + + let notification_block = receive_block(&mut ws, subscription_id).await.unwrap(); + assert_eq!(notification_block["block_number"], json!(1)); + + // this assertion is skipped due to CI instability + // assert_no_notifications(&mut ws).await; +} diff --git a/tests/integration/test_subscription_to_events.rs b/tests/integration/test_subscription_to_events.rs new file mode 100644 index 000000000..61e088b0e --- /dev/null +++ b/tests/integration/test_subscription_to_events.rs @@ -0,0 +1,442 @@ +use serde_json::json; +use starknet_core::constants::{STRK_ERC20_CONTRACT_ADDRESS, UDC_CONTRACT_ADDRESS}; +use starknet_rs_accounts::{Account, ExecutionEncoding, SingleOwnerAccount}; +use starknet_rs_core::types::{BlockId, BlockTag, Call, Felt, InvokeTransactionResult}; +use starknet_rs_core::utils::get_selector_from_name; +use starknet_rs_providers::jsonrpc::HttpTransport; +use starknet_rs_providers::JsonRpcClient; +use starknet_rs_signers::LocalWallet; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::constants; +use crate::common::utils::{ + assert_no_notifications, declare_v3_deploy_v3, + get_events_contract_in_sierra_and_compiled_class_hash, receive_notification, + receive_rpc_via_ws, subscribe, unsubscribe, SubscriptionId, +}; + +async fn subscribe_events( + ws: &mut WebSocketStream>, + params: serde_json::Value, +) -> Result { + subscribe(ws, "starknet_subscribeEvents", params).await +} + +async fn receive_event( + ws: &mut WebSocketStream>, + subscription_id: SubscriptionId, +) -> Result { + receive_notification(ws, "starknet_subscriptionEvents", subscription_id).await +} + +async fn get_single_owner_account( + devnet: &BackgroundDevnet, +) -> SingleOwnerAccount<&JsonRpcClient, LocalWallet> { + let (signer, account_address) = devnet.get_first_predeployed_account().await; + + SingleOwnerAccount::new( + &devnet.json_rpc_client, + signer, + account_address, + constants::CHAIN_ID, + ExecutionEncoding::New, + ) +} + +/// Returns deployment address. +async fn deploy_events_contract( + account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, +) -> Felt { + let (sierra, casm_hash) = get_events_contract_in_sierra_and_compiled_class_hash(); + + let (_, address) = declare_v3_deploy_v3(account, sierra, casm_hash, &[]).await.unwrap(); + address +} + +async fn emit_static_event( + account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, + contract_address: Felt, +) -> Result { + account + .execute_v3(vec![Call { + to: contract_address, + selector: get_selector_from_name("emit_event").unwrap(), + calldata: vec![Felt::ZERO], // what kind of event to emit + }]) + .send() + .await + .map_err(|e| anyhow::Error::msg(e.to_string())) +} + +fn static_event_key() -> Felt { + get_selector_from_name("StaticEvent").unwrap() +} + +#[tokio::test] +async fn event_subscription_with_no_params_until_unsubscription() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_id = subscribe_events(&mut ws, json!({})).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + // discard notifications emitted by system contracts - asserted in a separate test + receive_rpc_via_ws(&mut ws).await.unwrap(); // erc20 - fee charge + receive_rpc_via_ws(&mut ws).await.unwrap(); // udc - deployment + receive_rpc_via_ws(&mut ws).await.unwrap(); // erc20 - fee charge + + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + receive_rpc_via_ws(&mut ws).await.unwrap(); // erc20 - fee charge + assert_no_notifications(&mut ws).await; + + unsubscribe(&mut ws, subscription_id).await.unwrap(); + + emit_static_event(&account, contract_address).await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_only_from_filtered_address() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + let subscription_params = json!({ "from_address": contract_address }); + let subscription_id = subscribe_events(&mut ws, subscription_params).await.unwrap(); + + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_of_new_events_only_from_filtered_key() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + let subscription_params = json!({ "keys": [[static_event_key()]] }); + let subscription_id = subscribe_events(&mut ws, subscription_params).await.unwrap(); + + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_if_already_in_latest_block_in_on_tx_mode() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + let subscription_id = + subscribe_events(&mut ws, json!({ "from_address": contract_address })).await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_if_already_in_latest_block_in_on_demand_mode() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let mut account = get_single_owner_account(&devnet).await; + account.set_block_id(BlockId::Tag(BlockTag::Pending)); // for correct nonce in deployment + + let contract_address = deploy_events_contract(&account).await; + // to have declare+deploy in one block and invoke in another + devnet.create_block().await.unwrap(); + + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block_hash = devnet.create_block().await.unwrap(); + + let subscription_id = + subscribe_events(&mut ws, json!({ "from_address": contract_address })).await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block_hash, + "block_number": 2, // genesis = 0, then two block creations + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_only_when_moved_from_pending_to_latest_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let mut account = get_single_owner_account(&devnet).await; + account.set_block_id(BlockId::Tag(BlockTag::Pending)); // for correct nonce in deployment + + let contract_address = deploy_events_contract(&account).await; + // to have declare+deploy in one block and invoke in another + devnet.create_block().await.unwrap(); + + let subscription_id = + subscribe_events(&mut ws, json!({ "from_address": contract_address })).await.unwrap(); + + // only receive event when pending->latest + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + assert_no_notifications(&mut ws).await; + let latest_block_hash = devnet.create_block().await.unwrap(); + + let event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block_hash, + "block_number": 2, // genesis = 0, then two block creations + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_of_events_in_old_blocks_with_no_filters() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + let invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + // The declaration happens at block_number=1 so we query from there to latest + let subscription_params = json!({ "block_id": BlockId::Number(1) }); + let subscription_id = subscribe_events(&mut ws, subscription_params).await.unwrap(); + + // declaration of events contract fee charge + let declaration_fee_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!(declaration_fee_event["block_number"], 1); + assert_eq!(declaration_fee_event["from_address"], json!(STRK_ERC20_CONTRACT_ADDRESS)); + + // deployment of events contract: udc invocation + let deployment_udc_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!(deployment_udc_event["block_number"], 2); + assert_eq!(deployment_udc_event["from_address"], json!(UDC_CONTRACT_ADDRESS)); + + // deployment of events contract: fee charge + let deployment_fee_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!(deployment_fee_event["block_number"], 2); + assert_eq!(deployment_fee_event["from_address"], json!(STRK_ERC20_CONTRACT_ADDRESS),); + + // invocation of events contract + let invocation_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + invocation_event, + json!({ + "transaction_hash": invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + + // invocation of events contract fee charge + let invocation_fee_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!(invocation_fee_event["block_number"], latest_block.block_number); + assert_eq!(invocation_fee_event["from_address"], json!(STRK_ERC20_CONTRACT_ADDRESS)); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_of_old_and_new_events_with_address_filter() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + let old_invocation = emit_static_event(&account, contract_address).await.unwrap(); + let block_before_subscription = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + // The declaration happens at block_number=1, but only invocation should be notified of + let subscription_params = + json!({ "block_id": BlockId::Number(1), "from_address": contract_address }); + let subscription_id = subscribe_events(&mut ws, subscription_params).await.unwrap(); + + // assert presence of old event (event that was triggered before the subscription) + let old_invocation_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + old_invocation_event, + json!({ + "transaction_hash": old_invocation.transaction_hash, + "block_hash": block_before_subscription.block_hash, + "block_number": block_before_subscription.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + assert_no_notifications(&mut ws).await; + + // new event (after subscription) + let new_invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + let new_invocation_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + new_invocation_event, + json!({ + "transaction_hash": new_invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_of_old_and_new_events_with_key_filter() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + let old_invocation = emit_static_event(&account, contract_address).await.unwrap(); + let block_before_subscription = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + + // The declaration happens at block_number=1, but only invocation should be notified of + let subscription_params = + json!({ "block_id": BlockId::Number(1), "keys": [[static_event_key()]] }); + let subscription_id = subscribe_events(&mut ws, subscription_params).await.unwrap(); + + // assert presence of old event (event that was triggered before the subscription) + let invocation_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + invocation_event, + json!({ + "transaction_hash": old_invocation.transaction_hash, + "block_hash": block_before_subscription.block_hash, + "block_number": block_before_subscription.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + assert_no_notifications(&mut ws).await; + + // new event (after subscription) + let new_invocation = emit_static_event(&account, contract_address).await.unwrap(); + let latest_block = devnet.get_latest_block_with_tx_hashes().await.unwrap(); + let invocation_event = receive_event(&mut ws, subscription_id).await.unwrap(); + assert_eq!( + invocation_event, + json!({ + "transaction_hash": new_invocation.transaction_hash, + "block_hash": latest_block.block_hash, + "block_number": latest_block.block_number, + "from_address": contract_address, + "keys": [static_event_key()], + "data": [] + }) + ); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_of_events_in_too_old_blocks() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let account = get_single_owner_account(&devnet).await; + let contract_address = deploy_events_contract(&account).await; + + emit_static_event(&account, contract_address).await.unwrap(); + let last_block_hash = devnet.create_block().await.unwrap(); + + subscribe_events(&mut ws, json!({ "block_id": BlockId::Hash(last_block_hash) })).await.unwrap(); + + assert_no_notifications(&mut ws).await; +} diff --git a/tests/integration/test_subscription_to_pending_txs.rs b/tests/integration/test_subscription_to_pending_txs.rs new file mode 100644 index 000000000..7982b20ac --- /dev/null +++ b/tests/integration/test_subscription_to_pending_txs.rs @@ -0,0 +1,397 @@ +use std::collections::HashMap; + +use serde_json::json; +use starknet_core::constants::CHARGEABLE_ACCOUNT_ADDRESS; +use starknet_rs_accounts::{ExecutionEncoding, SingleOwnerAccount}; +use starknet_rs_core::types::{DeclareTransaction, Felt, InvokeTransaction, Transaction}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::constants; +use crate::common::utils::{ + assert_no_notifications, declare_v3_deploy_v3, + get_simple_contract_in_sierra_and_compiled_class_hash, receive_rpc_via_ws, subscribe, + unsubscribe, FeeUnit, SubscriptionId, +}; + +async fn subscribe_pending_txs( + ws: &mut WebSocketStream>, + params: serde_json::Value, +) -> Result { + subscribe(ws, "starknet_subscribePendingTransactions", params).await +} + +/// Modifies the provided value by leaving a `null` in place of the returned transaction. +fn extract_tx_from_notification( + notification: &mut serde_json::Value, +) -> Result { + let notification_result = notification["params"]["result"].take(); + serde_json::from_value(notification_result) +} + +#[tokio::test] +/// Both modes should notify of pending transactions, even though this never actually happens in +/// transaction mode. +async fn without_tx_details_happy_path() { + for block_mode in ["transaction", "demand"] { + let devnet_args = ["--block-generation-on", block_mode]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_params = json!({ "transaction_details": false, "sender_address": [] }); + let subscription_id = subscribe_pending_txs(&mut ws, subscription_params).await.unwrap(); + + let tx_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "result": tx_hash, + "subscription_id": subscription_id, + } + }) + ); + + assert_no_notifications(&mut ws).await; + } +} + +#[tokio::test] +async fn without_tx_details_happy_path_multiple_subscribers() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let subscription_params = json!({ "transaction_details": false, "sender_address": [] }); + let mut subscribers = HashMap::new(); + for _ in 0..2 { + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = + subscribe_pending_txs(&mut ws, subscription_params.clone()).await.unwrap(); + subscribers.insert(subscription_id, ws); + } + + let tx_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + for (subscription_id, mut ws) in subscribers { + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "result": tx_hash, + "subscription_id": subscription_id, + } + }) + ); + + assert_no_notifications(&mut ws).await; + } +} + +#[tokio::test] +/// Both modes should notify of pending transactions, even though this never actually happens in +/// transaction mode. +async fn with_tx_details_happy_path() { + for block_mode in ["transaction", "demand"] { + let devnet_args = ["--block-generation-on", block_mode]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_params = json!({ "transaction_details": true, "sender_address": [] }); + let subscription_id = subscribe_pending_txs(&mut ws, subscription_params).await.unwrap(); + + let mint_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + let mut notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + let notification_tx = extract_tx_from_notification(&mut notification).unwrap(); + + // Just assert hash to simplify testing. + assert_eq!(notification_tx.transaction_hash(), &mint_hash); + + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "subscription_id": subscription_id, + "result": null, + } + }) + ); + + assert_no_notifications(&mut ws).await; + } +} + +#[tokio::test] +async fn with_empty_body_happy_path() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_id = subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + + let tx_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "result": tx_hash, + "subscription_id": subscription_id, + } + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_stop_notifying_after_unsubscription() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let subscription_id = subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + + devnet.mint(Felt::ONE, 123).await; // dummy data + + receive_rpc_via_ws(&mut ws).await.unwrap(); + + let unsubscription = unsubscribe(&mut ws, subscription_id).await.unwrap(); + assert_eq!(unsubscription, json!({ "jsonrpc": "2.0", "id": 0, "result": true })); + + devnet.mint(Felt::TWO, 456).await; // dummy data + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn with_tx_details_and_filtered_address_happy_path() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (signer, account_address) = devnet.get_first_predeployed_account().await; + + let predeployed_account = SingleOwnerAccount::new( + &devnet.json_rpc_client, + signer.clone(), + account_address, + constants::CHAIN_ID, + ExecutionEncoding::New, + ); + + let subscription_id = subscribe_pending_txs( + &mut ws, + json!({ "transaction_details": true, "sender_address": [account_address] }), + ) + .await + .unwrap(); + + let (contract_class, casm_hash) = get_simple_contract_in_sierra_and_compiled_class_hash(); + + let (class_hash, _) = declare_v3_deploy_v3( + &predeployed_account, + contract_class.clone(), + casm_hash, + &[Felt::ONE], // dummy constructor + ) + .await + .unwrap(); + + let mut declaration_notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + let declaration_tx = extract_tx_from_notification(&mut declaration_notification).unwrap(); + match declaration_tx { + Transaction::Declare(DeclareTransaction::V3(tx)) => { + assert_eq!(tx.class_hash, class_hash); + assert_eq!(tx.nonce, Felt::ZERO); + } + other => panic!("Invalid tx: {other:?}"), + }; + + let mut deployment_notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + let deployment_tx = extract_tx_from_notification(&mut deployment_notification).unwrap(); + match deployment_tx { + Transaction::Invoke(InvokeTransaction::V3(tx)) => { + assert_eq!(tx.nonce, Felt::ONE); + } + other => panic!("Invalid tx: {other:?}"), + }; + + for notification in [declaration_notification, deployment_notification] { + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "subscription_id": subscription_id, + "result": null, + } + }) + ); + } + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_if_filtered_address_not_matched() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + // dummy address + subscribe_pending_txs(&mut ws, json!({ "sender_address": ["0x1"] })).await.unwrap(); + + devnet.mint(Felt::ONE, 123).await; // dummy data + + // nothing matched since minting is done via the Chargeable account + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_if_tx_by_filtered_address_already_in_pending_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let mint_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + // Minting is done by the Chargeable account + let acceptable_address = CHARGEABLE_ACCOUNT_ADDRESS; + let subscription_id = + subscribe_pending_txs(&mut ws, json!({ "sender_address": [acceptable_address] })) + .await + .unwrap(); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "result": mint_hash, + "subscription_id": subscription_id, + } + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_if_tx_by_filtered_address_in_latest_block_in_on_demand_mode() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.mint(Felt::ONE, 123).await; // dummy data + devnet.create_block().await.unwrap(); + + // Minting is done by the Chargeable account + let acceptable_address = CHARGEABLE_ACCOUNT_ADDRESS; + subscribe_pending_txs(&mut ws, json!({ "sender_address": [acceptable_address] })) + .await + .unwrap(); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_if_tx_by_filtered_address_in_latest_block_in_on_tx_mode() { + let devnet_args = ["--block-generation-on", "transaction"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + // Create tx and new block + devnet.mint(Felt::ONE, 123).await; // dummy data + + // Minting is done by the Chargeable account + let acceptable_address = CHARGEABLE_ACCOUNT_ADDRESS; + subscribe_pending_txs(&mut ws, json!({ "sender_address": [acceptable_address] })) + .await + .unwrap(); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_if_tx_already_in_pending_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let tx_hash = devnet.mint(Felt::ONE, 123).await; // dummy data + + // Subscribe AFTER the tx. + let subscription_id = subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionPendingTransactions", + "params": { + "result": tx_hash, + "subscription_id": subscription_id, + } + }) + ); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_if_tx_already_in_latest_block_in_on_demand_mode() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.mint(Felt::ONE, 123).await; // dummy data + devnet.create_block().await.unwrap(); + + // Subscribe AFTER the tx and block creation. + subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_if_tx_already_in_latest_block_in_on_tx_mode() { + let devnet_args = ["--block-generation-on", "transaction"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.mint(Felt::ONE, 123).await; // dummy data + + // Subscribe AFTER the tx and block creation. + subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_notify_on_read_request_if_txs_in_pending_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + subscribe_pending_txs(&mut ws, json!({})).await.unwrap(); + + let dummy_address = Felt::ONE; + devnet.mint(dummy_address, 123).await; // dummy data + + receive_rpc_via_ws(&mut ws).await.unwrap(); + + // read request should have no impact + devnet.get_balance_latest(&dummy_address, FeeUnit::Wei).await.unwrap(); + + assert_no_notifications(&mut ws).await; +} diff --git a/tests/integration/test_subscription_to_reorg.rs b/tests/integration/test_subscription_to_reorg.rs new file mode 100644 index 000000000..cbf5f81ee --- /dev/null +++ b/tests/integration/test_subscription_to_reorg.rs @@ -0,0 +1,119 @@ +use std::collections::{HashMap, HashSet}; + +use serde_json::json; +use starknet_rs_core::types::BlockId; +use tokio_tungstenite::connect_async; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::utils::{assert_no_notifications, receive_rpc_via_ws, subscribe, unsubscribe}; + +#[tokio::test] +async fn reorg_notification_for_all_subscriptions_except_pending_tx() { + let devnet_args = ["--state-archive-capacity", "full"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + // create blocks for later abortion + let starting_block_hash = devnet.create_block().await.unwrap(); + let ending_block_hash = devnet.create_block().await.unwrap(); + + let mut notifiable_subscribers = HashMap::new(); + for (subscription_method, subscription_params) in [ + ("starknet_subscribeNewHeads", json!({})), + ("starknet_subscribeTransactionStatus", json!({ "transaction_hash": "0x1" })), + ("starknet_subscribeEvents", json!({})), + ] { + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = + subscribe(&mut ws, subscription_method, subscription_params).await.unwrap(); + notifiable_subscribers.insert(subscription_id, ws); + } + + let (mut unnotifiable_ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + subscribe(&mut unnotifiable_ws, "starknet_subscribePendingTransactions", json!({})) + .await + .unwrap(); + + // assert that block-, tx_status- and event-subscribers got notified; unsubscribe them + devnet.abort_blocks(&BlockId::Hash(starting_block_hash)).await.unwrap(); + for (subscription_id, ws) in notifiable_subscribers.iter_mut() { + let notification = receive_rpc_via_ws(ws).await.unwrap(); + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionReorg", + "params": { + "result": { + "starting_block_hash": starting_block_hash, + "starting_block_number": 1, + "ending_block_hash": ending_block_hash, + "ending_block_number": 2, + }, + "subscription_id": subscription_id, + } + }) + ); + unsubscribe(ws, *subscription_id).await.unwrap(); + } + + // now that all sockets are unsubscribed, abort a new block and assert no notifications + let additional_block_hash = devnet.create_block().await.unwrap(); + devnet.abort_blocks(&BlockId::Hash(additional_block_hash)).await.unwrap(); + for (_, mut ws) in notifiable_subscribers { + assert_no_notifications(&mut ws).await; + } + + assert_no_notifications(&mut unnotifiable_ws).await; +} + +#[tokio::test] +async fn socket_with_n_subscriptions_should_get_n_reorg_notifications() { + let devnet_args = ["--state-archive-capacity", "full"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let created_block_hash = devnet.create_block().await.unwrap(); + + // Create one socket with n subscriptions. + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let mut subscription_ids = vec![]; + for subscription_method in ["starknet_subscribeNewHeads", "starknet_subscribeEvents"] { + let subscription_id = subscribe(&mut ws, subscription_method, json!({})).await.unwrap(); + subscription_ids.push(subscription_id); + } + + // Trigger reorg. + devnet.abort_blocks(&BlockId::Hash(created_block_hash)).await.unwrap(); + + // Assert n reorg notifications received. The notifications only differ in subscription_id. + let mut notification_ids = HashSet::new(); + for _ in subscription_ids.iter() { + let mut notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + + // Reorg notifications may be received in any order. To assert one reorg subscription + // was received per subscription_id, we extract the IDs from notifications, store them + // in a set, and later assert equality with the set of expected subscription IDs. + let notification_id = notification["params"]["subscription_id"].take().as_u64().unwrap(); + notification_ids.insert(notification_id); + + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionReorg", + "params": { + "result": { + "starting_block_hash": created_block_hash, + "starting_block_number": 1, + "ending_block_hash": created_block_hash, + "ending_block_number": 1, + }, + "subscription_id": null, + } + }) + ); + } + + assert_eq!(notification_ids, HashSet::from_iter(subscription_ids)); + + assert_no_notifications(&mut ws).await; +} diff --git a/tests/integration/test_subscription_to_tx_status.rs b/tests/integration/test_subscription_to_tx_status.rs new file mode 100644 index 000000000..3a35f4064 --- /dev/null +++ b/tests/integration/test_subscription_to_tx_status.rs @@ -0,0 +1,329 @@ +use serde_json::json; +use starknet_rs_core::types::{BlockId, BlockTag, Felt}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::utils::{ + assert_no_notifications, receive_rpc_via_ws, subscribe, subscribe_new_heads, unsubscribe, + SubscriptionId, +}; + +async fn subscribe_tx_status( + ws: &mut WebSocketStream>, + tx_hash: &Felt, + block_id: Option, +) -> Result { + let mut params = json!({ "transaction_hash": tx_hash }); + + if let Some(block_id) = block_id { + params["block_id"] = json!(block_id); + } + + subscribe(ws, "starknet_subscribeTransactionStatus", params).await +} + +/// Returns (address, amount, tx_hash), with tx_hash being the hash of the minting tx if it's +/// the first thing done on a fresh Devnet using the returned `address` and `amount`. +fn first_mint_data() -> (Felt, u128, Felt) { + let expected_tx_hash = Felt::from_hex_unchecked( + "0x2c13842a63d019b76805465c3cca99035ac82488856e7763e78427513021a13", + ); + (Felt::ONE, 10, expected_tx_hash) +} + +fn assert_successful_mint_notification( + notification: serde_json::Value, + tx_hash: Felt, + subscription_id: SubscriptionId, +) { + assert_eq!( + notification, + json!({ + "jsonrpc": "2.0", + "method": "starknet_subscriptionTransactionStatus", + "params": { + "result": { + "transaction_hash": tx_hash, + "status": { + "finality_status": "ACCEPTED_ON_L2", + "failure_reason": null, + "execution_status": "SUCCEEDED", + }, + }, + "subscription_id": subscription_id, + } + }) + ); +} + +#[tokio::test] +async fn subscribe_to_new_tx_status_happy_path() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + let subscription_id = subscribe_tx_status(&mut ws, &expected_tx_hash, None).await.unwrap(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); +} + +#[tokio::test] +async fn should_stop_notifying_after_unsubscription() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + // subscribe and immediately unsubscribe + let subscription_id = subscribe_tx_status(&mut ws, &expected_tx_hash, None).await.unwrap(); + let unsubscription = unsubscribe(&mut ws, subscription_id).await.unwrap(); + assert_eq!(unsubscription, json!({ "jsonrpc": "2.0", "id": 0, "result": true })); + + devnet.mint(address, mint_amount).await; + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_receive_notification_if_not_subscribed() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.mint(0x1, 1).await; + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_receive_notification_if_subscribed_to_another_tx() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let dummy_tx_hash = Felt::ONE; + subscribe_tx_status(&mut ws, &dummy_tx_hash, None).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + assert_ne!(tx_hash, dummy_tx_hash); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_receive_tx_notification_if_subscribed_to_blocks() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + subscribe_new_heads(&mut ws, json!({})).await.unwrap(); + + devnet.mint(0x1, 1).await; + + // there should only be a single new block notification since minting is a block-adding tx + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!(notification["method"], "starknet_subscriptionNewHeads"); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_not_receive_block_notification_if_subscribed_to_tx() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + subscribe_tx_status(&mut ws, &Felt::ONE, None).await.unwrap(); + + devnet.create_block().await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_only_when_tx_moved_from_pending_to_latest_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + let block_tag = BlockId::Tag(BlockTag::Latest); + + // should work if subscribing before sending the tx + let (mut ws_before_tx, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id_before = + subscribe_tx_status(&mut ws_before_tx, &expected_tx_hash, Some(block_tag)).await.unwrap(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + assert_no_notifications(&mut ws_before_tx).await; + + // should work even if subscribing after the tx was sent + let (mut ws_after_tx, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id_after = + subscribe_tx_status(&mut ws_after_tx, &expected_tx_hash, Some(block_tag)).await.unwrap(); + assert_no_notifications(&mut ws_after_tx).await; + + // move tx from pending to latest + devnet.create_block().await.unwrap(); + + for (subscription_id, mut ws) in + [(subscription_id_before, ws_before_tx), (subscription_id_after, ws_after_tx)] + { + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); + assert_no_notifications(&mut ws).await; + } +} + +#[tokio::test] +async fn should_notify_pending_subscriber_if_tx_already_in_pending_block() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + let (mut ws_pending, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = subscribe_tx_status( + &mut ws_pending, + &expected_tx_hash, + Some(BlockId::Tag(BlockTag::Pending)), + ) + .await + .unwrap(); + + let notification = receive_rpc_via_ws(&mut ws_pending).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); + + // expect no notifications if subscribing to latest block and latest not yet mined + let (mut ws_latest, _) = connect_async(devnet.ws_url()).await.unwrap(); + subscribe_tx_status(&mut ws_latest, &expected_tx_hash, Some(BlockId::Tag(BlockTag::Latest))) + .await + .unwrap(); + assert_no_notifications(&mut ws_latest).await; +} + +#[tokio::test] +async fn should_notify_if_tx_already_in_latest_block_and_subscribed_to_latest() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + devnet.create_block().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + let subscription_id = + subscribe_tx_status(&mut ws, &expected_tx_hash, Some(BlockId::Tag(BlockTag::Latest))) + .await + .unwrap(); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); +} + +#[tokio::test] +async fn should_not_notify_if_tx_already_in_latest_block_and_subscribed_to_pending() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + devnet.create_block().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + subscribe_tx_status(&mut ws, &expected_tx_hash, Some(BlockId::Tag(BlockTag::Pending))) + .await + .unwrap(); + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_never_notify_if_block_id_newer_than_tx_addition() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + let newer_block_hash = devnet.create_block().await.unwrap(); + subscribe_tx_status(&mut ws, &tx_hash, Some(BlockId::Hash(newer_block_hash))).await.unwrap(); + + assert_no_notifications(&mut ws).await; + + // regardless of how many blocks are created, no notifications are expected + devnet.create_block().await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_when_tx_in_pending_if_legal_numeric_block_id() { + let devnet_args = ["--block-generation-on", "demand"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + let subscription_id = + subscribe_tx_status(&mut ws, &expected_tx_hash, Some(BlockId::Number(0))).await.unwrap(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); + + // should only have notified for adding the tx in pending, not now when latest is created + devnet.create_block().await.unwrap(); + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn should_notify_when_tx_in_latest_if_legal_numeric_block_id() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, mint_amount, expected_tx_hash) = first_mint_data(); + let subscription_id = + subscribe_tx_status(&mut ws, &expected_tx_hash, Some(BlockId::Number(0))).await.unwrap(); + + let tx_hash = devnet.mint(address, mint_amount).await; + assert_eq!(tx_hash, expected_tx_hash); + + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); +} + +#[tokio::test] +async fn should_not_notify_of_status_change_when_block_aborted() { + let devnet_args = ["--state-archive-capacity", "full"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let (address, amount, _) = first_mint_data(); + let tx_hash = devnet.mint(address, amount).await; + let subscription_id = subscribe_tx_status(&mut ws, &tx_hash, None).await.unwrap(); + + // as expected, the actual tx accepted notification is first + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_successful_mint_notification(notification, tx_hash, subscription_id); + + devnet.abort_blocks(&BlockId::Number(1)).await.unwrap(); + + // only expect reorg subscription + let notification = receive_rpc_via_ws(&mut ws).await.unwrap(); + assert_eq!(notification["method"], "starknet_subscriptionReorg"); + assert_no_notifications(&mut ws).await; +} diff --git a/tests/integration/test_subscription_with_invalid_block_id.rs b/tests/integration/test_subscription_with_invalid_block_id.rs new file mode 100644 index 000000000..ab502e82a --- /dev/null +++ b/tests/integration/test_subscription_with_invalid_block_id.rs @@ -0,0 +1,83 @@ +use serde_json::json; +use starknet_rs_core::types::{BlockId, Felt}; +use tokio_tungstenite::connect_async; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::utils::{assert_no_notifications, send_text_rpc_via_ws}; + +fn block_not_found_error() -> serde_json::Value { + json!({ "jsonrpc": "2.0", "id": 0, "error": { "code": 24, "message": "Block not found" } }) +} + +fn call_on_pending_error() -> serde_json::Value { + json!({ "jsonrpc": "2.0", "id": 0, "error": { "code": 69, "message": "This method does not support being called on the pending block" } }) +} + +#[tokio::test] +async fn test_subscribing_to_non_existent_block() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + // Cartesian product: (subscription_method, subscription_params) x invalid_block_id + for (subscription_method, mut subscription_params) in [ + ("starknet_subscribeNewHeads", json!({})), + ("starknet_subscribeTransactionStatus", json!({ "transaction_hash": "0x1" })), + ("starknet_subscribeEvents", json!({})), + ] { + for block_id in [BlockId::Number(1), BlockId::Hash(Felt::ONE)] { + subscription_params["block_id"] = json!(block_id); + let subscription_resp = + send_text_rpc_via_ws(&mut ws, subscription_method, subscription_params.clone()) + .await + .unwrap(); + + assert_eq!(subscription_resp, block_not_found_error()) + } + } + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn test_aborted_blocks_not_subscribable() { + let devnet_args = ["--state-archive-capacity", "full"]; + let devnet = BackgroundDevnet::spawn_with_additional_args(&devnet_args).await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let created_block_hash = devnet.create_block().await.unwrap(); + devnet.abort_blocks(&BlockId::Hash(created_block_hash)).await.unwrap(); + + // Cartesian product: (subscription_method, subscription_params) x invalid_block_id + for (subscription_method, mut subscription_params) in [ + ("starknet_subscribeNewHeads", json!({})), + ("starknet_subscribeTransactionStatus", json!({ "transaction_hash": "0x1" })), + ("starknet_subscribeEvents", json!({})), + ] { + for block_id in [BlockId::Number(1), BlockId::Hash(created_block_hash)] { + subscription_params["block_id"] = json!(block_id); + let subscription_resp = + send_text_rpc_via_ws(&mut ws, subscription_method, subscription_params.clone()) + .await + .unwrap(); + + assert_eq!(subscription_resp, block_not_found_error()) + } + } + + assert_no_notifications(&mut ws).await; +} + +#[tokio::test] +async fn test_pending_block_not_allowed_in_block_and_event_subscription() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + for subscription_method in ["starknet_subscribeNewHeads", "starknet_subscribeEvents"] { + let subscription_resp = + send_text_rpc_via_ws(&mut ws, subscription_method, json!({ "block_id": "pending" })) + .await + .unwrap(); + + assert_eq!(subscription_resp, call_on_pending_error(), "Method: {subscription_method}"); + } +} diff --git a/tests/integration/test_transaction_handling.rs b/tests/integration/test_transaction_handling.rs index 6924b8950..cae7e4cc2 100644 --- a/tests/integration/test_transaction_handling.rs +++ b/tests/integration/test_transaction_handling.rs @@ -4,12 +4,18 @@ use server::test_utils::assert_contains; use starknet_core::utils::exported_test_utils::dummy_cairo_0_contract_class; use starknet_rs_accounts::{Account, AccountError, ExecutionEncoding, SingleOwnerAccount}; use starknet_rs_core::types::contract::legacy::LegacyContractClass; -use starknet_rs_core::types::{Felt, StarknetError}; +use starknet_rs_core::types::{Call, Felt, InvokeTransactionResult, StarknetError}; +use starknet_rs_core::utils::get_selector_from_name; use starknet_rs_providers::ProviderError; use crate::common::background_devnet::BackgroundDevnet; -use crate::common::constants::{CHAIN_ID, INVALID_ACCOUNT_SIERRA_PATH}; -use crate::common::utils::get_simple_contract_in_sierra_and_compiled_class_hash; +use crate::common::constants::{ + CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH, CHAIN_ID, INVALID_ACCOUNT_SIERRA_PATH, +}; +use crate::common::utils::{ + declare_v3_deploy_v3, get_flattened_sierra_contract_and_casm_hash, + get_simple_contract_in_sierra_and_compiled_class_hash, +}; #[tokio::test] async fn test_failed_validation_with_expected_message() { @@ -76,3 +82,47 @@ async fn test_declaration_rejected_if_casm_hash_not_matching() { other => panic!("Unexpected response: {other:?}"), } } + +#[tokio::test] +async fn test_tx_status_content_of_failed_invoke() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + + let (signer, account_address) = devnet.get_first_predeployed_account().await; + let account = SingleOwnerAccount::new( + &devnet.json_rpc_client, + signer, + account_address, + CHAIN_ID, + ExecutionEncoding::New, + ); + + let (sierra, casm_hash) = + get_flattened_sierra_contract_and_casm_hash(CAIRO_1_PANICKING_CONTRACT_SIERRA_PATH); + + let (_, contract_address) = + declare_v3_deploy_v3(&account, sierra, casm_hash, &[]).await.unwrap(); + + let InvokeTransactionResult { transaction_hash } = account + .execute_v1(vec![Call { + to: contract_address, + selector: get_selector_from_name("create_panic").unwrap(), + calldata: vec![], + }]) + .max_fee(Felt::from(1e18 as u128)) + .send() + .await + .unwrap(); + + // TODO sending a custom request until starknet-rs is adapted to include failure reason + let tx_status = devnet + .send_custom_rpc( + "starknet_getTransactionStatus", + serde_json::json!({ "transaction_hash": transaction_hash }), + ) + .await + .unwrap(); + + assert_eq!(tx_status["finality_status"], "ACCEPTED_ON_L2"); + assert_contains(tx_status["failure_reason"].as_str().unwrap(), "Error in the called contract"); + assert_eq!(tx_status["execution_status"], "REVERTED"); +} diff --git a/tests/integration/test_websocket.rs b/tests/integration/test_websocket.rs new file mode 100644 index 000000000..7a9ff2e97 --- /dev/null +++ b/tests/integration/test_websocket.rs @@ -0,0 +1,137 @@ +use serde_json::json; +use starknet_rs_core::types::Felt; +use tokio_tungstenite::connect_async; + +use crate::common::background_devnet::BackgroundDevnet; +use crate::common::utils::{ + assert_no_notifications, send_binary_rpc_via_ws, send_text_rpc_via_ws, subscribe, FeeUnit, +}; + +#[tokio::test] +/// Testing for all non-ws methods would be longsome, so we just test for one devnet_ and one +/// starknet_ method +async fn test_general_rpc_support_via_websocket_is_disabled() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let expected_resp = + json!({"jsonrpc":"2.0", "id":0, "error":{"code":-32601, "message":"Method not found"}}); + + assert_eq!( + send_text_rpc_via_ws(&mut ws, "devnet_mint", json!({})).await.unwrap(), + expected_resp, + ); + + assert_eq!( + send_text_rpc_via_ws(&mut ws, "starknet_syncing", json!({})).await.unwrap(), + expected_resp, + ); +} + +#[tokio::test] +#[ignore = "General RPC support via websocket is disabled"] +async fn mint_and_check_tx_via_websocket() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let mint_resp = send_text_rpc_via_ws( + &mut ws, + "devnet_mint", + json!({ + "address": "0x1", + "amount": 100, + "unit": "WEI", + }), + ) + .await + .unwrap(); + + let tx_hash = mint_resp["result"]["tx_hash"].as_str().unwrap(); + + let tx = send_text_rpc_via_ws( + &mut ws, + "starknet_getTransactionByHash", + json!({ "transaction_hash": tx_hash }), + ) + .await + .unwrap(); + + assert!(tx["result"].is_object()); +} + +#[tokio::test] +#[ignore = "General RPC support via websocket is disabled"] +async fn create_block_via_binary_ws_message() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let block_specifier = json!({ "block_id": "latest" }); + let block_resp_before = + send_binary_rpc_via_ws(&mut ws, "starknet_getBlockWithTxs", block_specifier.clone()) + .await + .unwrap(); + assert_eq!(block_resp_before["result"]["block_number"], 0); + + let creation_resp = + send_binary_rpc_via_ws(&mut ws, "devnet_createBlock", json!({})).await.unwrap(); + assert!(creation_resp["result"].is_object()); + + let block_resp_after = + send_binary_rpc_via_ws(&mut ws, "starknet_getBlockWithTxs", block_specifier).await.unwrap(); + assert_eq!(block_resp_after["result"]["block_number"], 1); +} + +#[tokio::test] +#[ignore = "General RPC support via websocket is disabled"] +async fn multiple_ws_connections() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let iterations = 10; + + let mut ws_streams = vec![]; + for _ in 0..iterations { + let (ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + ws_streams.push(ws); + } + + let dummy_address: &str = "0x1"; + let single_mint_amount = 10; + for ws in &mut ws_streams { + send_text_rpc_via_ws( + ws, + "devnet_mint", + json!({ "address": dummy_address, "amount": single_mint_amount }), + ) + .await + .unwrap(); + } + + let balance = devnet + .get_balance_latest(&Felt::from_hex_unchecked(dummy_address), FeeUnit::Wei) + .await + .unwrap(); + assert_eq!(balance, Felt::from(single_mint_amount * iterations)); +} + +#[tokio::test] +#[ignore = "General RPC support via websocket is disabled"] +async fn invalid_request() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + let resp = send_text_rpc_via_ws(&mut ws, "devnet_mint", json!({})).await.unwrap(); + assert_eq!(resp["error"]["message"], "missing field `address`"); +} + +#[tokio::test] +async fn restarting_should_forget_all_websocket_subscriptions() { + let devnet = BackgroundDevnet::spawn().await.unwrap(); + let (mut ws, _) = connect_async(devnet.ws_url()).await.unwrap(); + + devnet.create_block().await.unwrap(); + + subscribe(&mut ws, "starknet_subscribeNewHeads", json!({})).await.unwrap(); + + devnet.restart().await; + + assert_no_notifications(&mut ws).await; +} diff --git a/website/docs/api.md b/website/docs/api.md index 6694a3775..f1d65138c 100644 --- a/website/docs/api.md +++ b/website/docs/api.md @@ -26,6 +26,17 @@ New features are only supported as part of the JSON-RPC API. Older non-RPC reque To check if a Devnet instance is alive, send an HTTP request `GET /is_alive`. If alive, the Devnet will reply with a `200 OK` and an appropriate message. +### WebSocket + +JSON-RPC websocket methods can be accessed via the WebSocket protocol. Devnet is listening for new WebSocket connections at `ws://:/ws` (notice the protocol scheme). Any request body you would send to `/rpc` you can send as a text (or binary) message via WebSocket. E.g. using [`wscat`](https://www.npmjs.com/package/wscat) on the same computer where Devnet is spawned at default host and port: + +``` +$ wscat -c ws://127.0.0.1:5050/ws +Connected (press CTRL+C to quit) +> { "jsonrpc": "2.0", "id": 0, "method": "starknet_subscribeNewHeads" } +< {"id":0,"result":2935616350010920547,"jsonrpc":"2.0"} +``` + ## Interacting with Devnet in JavaScript and TypeScript To spawn Devnet and interact with it using the [Devnet API](#devnet-api), you can use [`starknet-devnet-js`](https://github.com/0xSpaceShard/starknet-devnet-js/). This can be especially useful in achieving [L1-L2 communication](./postman.md#l1-l2-interaction-via-postman). diff --git a/website/docs/blocks.md b/website/docs/blocks.md index 49293970c..3deb1e642 100644 --- a/website/docs/blocks.md +++ b/website/docs/blocks.md @@ -101,6 +101,12 @@ Aborted blocks can only be queried by block hash. Devnet does not support the ab - already aborted blocks - Devnet's genesis block +### Websocket subscription notifications + +On block abortion, a `starknet_subscriptionReorg` notification will be sent to all websocket subscribers requiring so according to [JSON-RPC websocket API specification](https://github.com/starkware-libs/starknet-specs/blob/v0.8.0-rc1/api/starknet_ws_api.json#L256). The `starting_block` of the orphaned chain is the successor of the new latest block and the `ending_block` of the orphaned chain is the block that was latest before aborting. One reorg notification is sent per subscription, not per websocket, meaning that if a websocket has n subscriptions, it will receive n reorg notifications, each with its own subscription ID. + +If a socket has subscribed to transaction status changes of a transaction `tx1` using `starknet_subscribeTransactionStatus` and the block holding `tx1` gets aborted, a `starknet_subscriptionTransactionStatus` notification shall NOT be sent. The socket shall have to rely on handling `starknet_subscriptionReorg`. + ### Request and response To abort, send one of the following: diff --git a/website/docs/dump-load-restart.md b/website/docs/dump-load-restart.md index 157cce749..45748990b 100644 --- a/website/docs/dump-load-restart.md +++ b/website/docs/dump-load-restart.md @@ -103,7 +103,7 @@ If you dumped a Devnet utilizing one class for account predeployment (e.g. `--ac ## Restarting -Devnet can be restarted by making a `POST /restart` request (no body required) or `JSON-RPC` request with method name `devnet_restart`. All of the deployed contracts (including predeployed), blocks and storage updates will be restarted to the original state, without the transactions and requests that may have been loaded from a dump file on startup. +Devnet can be restarted by making a `POST /restart` request (no body required) or `JSON-RPC` request with method name `devnet_restart`. All of the deployed contracts (including predeployed), blocks and storage updates will be restarted to the original state, without the transactions and requests that may have been loaded from a dump file on startup. Websocket subscriptions will also be forgotten. ### Restarting and L1-L2 messaging diff --git a/website/docs/intro.md b/website/docs/intro.md index 41e3e7c34..edafd281c 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -7,7 +7,9 @@ sidebar_position: 1 :::danger Difference disclaimer - Devnet should not be used as a replacement for official testnets. After testing on Devnet, be sure to test on a testnet (alpha-sepolia)! -- Block states are not committed in a Merke-Patricia trie or a similar tree-like structure. Block roots are therefore set to 0. +- Devnet does not organize state data into Merkle-Patricia tries or similar tree-like structures, so: + - calling the `starknet_getStorageProof` RPC method shall always result in `STORAGE_PROOF_NOT_SUPPORTED` + - block roots are set to 0 - The semantics of `REJECTED` and `REVERTED` status of a transaction is not the same as on the official testnet: | Tx status | Official testnet | Devnet | diff --git a/website/docs/postman.md b/website/docs/postman.md index 8d59a4e05..5710cf230 100644 --- a/website/docs/postman.md +++ b/website/docs/postman.md @@ -119,7 +119,7 @@ A running L1 node is **not** required for this operation. ::: -Sends a mock transactions to L2, as if coming from L1, without the need for running L1. The deployed L2 contract address `l2_contract_address` and `entry_point_selector` must be valid, otherwise a new block will not be created. +Sends a mock transactions to L2, as if coming from L1, without the need for running L1. The deployed L2 contract address `l2_contract_address` and `entry_point_selector` must be valid, otherwise a new block will not be created. The `l1_transaction_hash` property is optional and, if provided, enables future `starknet_getMessagesStatus` requests with that hash value provided. Normally `nonce` is calculated by the L1 Starknet contract and it is used in L1 and L2. In this case, it needs to be provided manually. @@ -139,7 +139,8 @@ Request: "0x2" ], "paid_fee_on_l1": "0x123456abcdef", - "nonce":"0x0" + "nonce":"0x0", + "l1_transaction_hash": "0x000abc123", // optional } ``` @@ -158,7 +159,8 @@ JSON-RPC "0x2" ], "paid_fee_on_l1": "0x123456abcdef", - "nonce":"0x0" + "nonce":"0x0", + "l1_transaction_hash": "0x000abc123", // optional } } ```