From d6c909b15cb8f4fd4c0bc703460e7abc2252ad66 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 19 Dec 2024 19:18:00 +0400 Subject: [PATCH 01/42] add motsu multiple contract deployment --- Cargo.lock | 1087 +++++++++++++++++++++++++++++++-- Cargo.toml | 16 +- crates/motsu-proc/src/test.rs | 10 +- crates/motsu/Cargo.toml | 2 + crates/motsu/README.md | 63 ++ crates/motsu/src/context.rs | 369 ++++++++++- crates/motsu/src/lib.rs | 103 ++++ crates/motsu/src/prelude.rs | 2 +- crates/motsu/src/shims.rs | 52 +- 9 files changed, 1603 insertions(+), 101 deletions(-) create mode 100644 crates/motsu/README.md diff --git a/Cargo.lock b/Cargo.lock index 8661feb..ac059bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,29 +13,54 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.6" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ + "alloy-rlp", + "arbitrary", "bytes", "cfg-if", "const-hex", + "derive_arbitrary", "derive_more", + "foldhash", + "getrandom", "hex-literal", + "indexmap", "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "proptest-derive", + "rand", "ruint", + "rustc-hash", + "serde", + "sha3", "tiny-keccak", ] +[[package]] +name = "alloy-rlp" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "alloy-sol-macro" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.90", @@ -43,15 +68,15 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", "indexmap", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.90", @@ -61,9 +86,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" dependencies = [ "const-hex", "dunce", @@ -76,27 +101,213 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.7.6" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" +checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" dependencies = [ "alloy-primitives", "alloy-sol-macro", "const-hex", ] +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -106,6 +317,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byteorder" version = "1.5.0" @@ -118,6 +335,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cc" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -138,10 +364,10 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -173,6 +399,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -197,6 +435,16 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -208,17 +456,45 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "derive_more" -version = "0.99.18" +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 = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", "syn 2.0.90", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", ] [[package]] @@ -228,7 +504,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -237,12 +515,118 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -251,6 +635,35 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", ] [[package]] @@ -283,22 +696,74 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "indexmap" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ + "arbitrary", "equivalent", "hashbrown 0.15.2", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + [[package]] name = "keccak" version = "0.1.5" @@ -308,6 +773,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-const" version = "0.2.0" @@ -332,6 +807,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -348,19 +829,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "mini-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14eacc4bfcf10da9b6d5185ef51b2dc75434afac34b4d8aabe40f6b141ee071c" -dependencies = [ - "cfg-if", -] - [[package]] name = "motsu" version = "0.2.1" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "const-hex", "dashmap", "motsu-proc", @@ -382,6 +856,25 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -398,6 +891,32 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking_lot_core" version = "0.9.10" @@ -417,6 +936,27 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -426,6 +966,26 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -450,6 +1010,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -465,14 +1047,37 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ + "bit-set", + "bit-vec", "bitflags", + "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.37" @@ -482,12 +1087,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -506,6 +1119,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rand_xorshift" @@ -554,14 +1170,45 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "ruint" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ + "alloy-rlp", + "arbitrary", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", "proptest", "rand", + "rlp", "ruint-macro", "serde", "valuable", @@ -574,27 +1221,114 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +dependencies = [ + "rand", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.24", +] + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.216" @@ -615,16 +1349,74 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -632,29 +1424,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "stylus-proc" -version = "0.6.0" +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd02e91dffe7b73df84a861c992494d6b72054bc9a17fe73e147e34e9a64ef3" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stylus-proc" +version = "0.7.0-beta.1" +source = "git+https://github.com/qalisander/stylus-sdk-rs?branch=fix-encoding-in-sol-interface#3025bcb2a803ad08dc0c077dfc81465a54d9fb29" dependencies = [ "alloy-primitives", "alloy-sol-types", "cfg-if", - "convert_case 0.6.0", + "convert_case", "lazy_static", + "proc-macro-error", "proc-macro2", "quote", "regex", "sha3", - "syn 1.0.109", + "syn 2.0.90", "syn-solidity", + "trybuild", ] [[package]] name = "stylus-sdk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26042693706e29fb7e3cf3d71c99534ac97fca98b6f81ba77ab658022ab2e210" +version = "0.7.0-beta.1" +source = "git+https://github.com/qalisander/stylus-sdk-rs?branch=fix-encoding-in-sol-interface#3025bcb2a803ad08dc0c077dfc81465a54d9fb29" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -663,10 +1471,15 @@ dependencies = [ "hex", "keccak-const", "lazy_static", - "mini-alloc", "stylus-proc", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -691,9 +1504,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.7" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" dependencies = [ "paste", "proc-macro2", @@ -701,6 +1514,60 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-triple" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -710,12 +1577,79 @@ dependencies = [ "crunchy", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "trybuild" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -734,6 +1668,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "valuable" version = "0.1.0" @@ -746,6 +1686,39 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -810,6 +1783,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -836,3 +1827,17 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] diff --git a/Cargo.toml b/Cargo.toml index ad77658..65c47aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,13 +23,13 @@ all = "warn" const-hex = { version = "1.11.1", default-features = false } once_cell = "1.19.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } -stylus-sdk = "0.6.0" +stylus-sdk = { version = "0.7.0-beta.1", default-features = false } dashmap = "6.1.0" syn = { version = "2.0.58", features = ["full"] } proc-macro2 = "1.0.79" quote = "1.0.35" -alloy-primitives = { version = "=0.7.6", default-features = false } -alloy-sol-types = { version = "=0.7.6", default-features = false } +alloy-primitives = { version = "0.8.14", default-features = false } +alloy-sol-types = { version = "0.8.14", default-features = false } # members motsu = { path = "crates/motsu" } @@ -55,4 +55,14 @@ default = { extend-ignore-identifiers-re = [ "[0-9a-fA-F][0-9a-fA-F]", ] } +## TODO#q: remove this once the fix is released +[patch.crates-io.stylus-sdk] +git = "https://github.com/qalisander/stylus-sdk-rs" +branch = "fix-encoding-in-sol-interface" + +[patch.crates-io.stylus-proc] +git = "https://github.com/qalisander/stylus-sdk-rs" +branch = "fix-encoding-in-sol-interface" + + diff --git a/crates/motsu-proc/src/test.rs b/crates/motsu-proc/src/test.rs index 2e88961..8036c96 100644 --- a/crates/motsu-proc/src/test.rs +++ b/crates/motsu-proc/src/test.rs @@ -15,11 +15,6 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { let fn_block = &item_fn.block; let fn_args = &sig.inputs; - // Currently, more than one contract per unit test is not supported. - if fn_args.len() > 1 { - error!(fn_args, "expected at most one contract in test signature"); - } - // Whether 1 or none contracts will be declared. let arg_binding_and_ty = match fn_args .into_iter() @@ -42,7 +37,7 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { // Test case assumes, that contract's variable has `&mut` reference // to contract's type. quote! { - #arg_binding: &mut #contract_ty + #arg_binding: #contract_ty } }); @@ -50,7 +45,7 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { arg_binding_and_ty.iter().map(|(_arg_binding, contract_ty)| { // Pass mutable reference to the contract. quote! { - &mut <#contract_ty>::default() + <#contract_ty>::default() } }); @@ -61,7 +56,6 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { #( #attrs )* #[test] fn #fn_name() #fn_return_type { - use ::motsu::prelude::DefaultStorage; let test = | #( #contract_arg_defs ),* | #fn_block; let res = test( #( #contract_args ),* ); ::motsu::prelude::Context::current().reset_storage(); diff --git a/crates/motsu/Cargo.toml b/crates/motsu/Cargo.toml index d2751d2..8094616 100644 --- a/crates/motsu/Cargo.toml +++ b/crates/motsu/Cargo.toml @@ -15,6 +15,8 @@ tiny-keccak.workspace = true stylus-sdk.workspace = true motsu-proc.workspace = true dashmap.workspace = true +alloy-primitives = { workspace = true, features = ["arbitrary", "rand"] } +alloy-sol-types.workspace = true [lints] workspace = true diff --git a/crates/motsu/README.md b/crates/motsu/README.md new file mode 100644 index 0000000..f7f1067 --- /dev/null +++ b/crates/motsu/README.md @@ -0,0 +1,63 @@ +# Motsu (持つ) - Unit Testing for Stylus + +This crate enables unit-testing for Stylus contracts. It abstracts away the +machinery necessary for writing tests behind a `#[motsu::test]` procedural +macro. + +`motsu` means ["to hold"](https://jisho.org/word/%E6%8C%81%E3%81%A4) in +Japanese -- we hold a stylus in our hand. + +## Usage + +Annotate tests with `#[motsu::test]` instead of `#[test]` to get access to VM +affordances. + +Note that we require contracts to implement `stylus_sdk::prelude::StorageType`. +This trait is typically implemented by default with `stylus_proc::sol_storage` +or `stylus_proc::storage` macros. + +```rust +#[cfg(test)] +mod tests { + use contracts::token::erc20::Erc20; + + #[motsu::test] + fn reads_balance(contract: Erc20) { + let balance = contract.balance_of(Address::ZERO); // Access storage. + assert_eq!(balance, U256::ZERO); + } +} +``` + +Annotating a test function that accepts no parameters will make `#[motsu::test]` +behave the same as `#[test]`. + +```rust,ignore +#[cfg(test)] +mod tests { + #[motsu::test] + fn t() { // If no params, it expands to a `#[test]`. + // ... + } +} +``` + +Note that currently, test suites using `motsu::test` will run serially because +of global access to storage. + +### Notice + +We maintain this crate on a best-effort basis. We use it extensively on our own +tests, so we will add here any symbols we may need. However, since we expect +this to be a temporary solution, don't expect us to address all requests. + +That being said, please do open an issue to start a discussion, keeping in mind +our [code of conduct] and [contribution guidelines]. + +[code of conduct]: ../../CODE_OF_CONDUCT.md + +[contribution guidelines]: ../../CONTRIBUTING.md + +## Security + +Refer to our [Security Policy](../../SECURITY.md) for more details. diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 381d504..790517a 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -1,10 +1,16 @@ //! Unit-testing context for Stylus contracts. -use std::{collections::HashMap, ptr}; +use std::{borrow::BorrowMut, collections::HashMap, ptr, slice, sync::Mutex}; -use dashmap::DashMap; +use alloy_primitives::Address; +use dashmap::{mapref::one::RefMut, DashMap}; use once_cell::sync::Lazy; -use stylus_sdk::{alloy_primitives::uint, prelude::StorageType}; +use stylus_sdk::{ + abi::Router, + alloy_primitives::uint, + prelude::{StorageType, TopLevelStorage}, + ArbResult, +}; use crate::prelude::{Bytes32, WORD_BYTES}; @@ -21,12 +27,6 @@ impl Context { Self { thread_name: ThreadName::current() } } - /// Get the value at `key` in storage. - pub(crate) fn get_bytes(self, key: &Bytes32) -> Bytes32 { - let storage = STORAGE.entry(self.thread_name).or_default(); - storage.contract_data.get(key).copied().unwrap_or_default() - } - /// Get the raw value at `key` in storage and write it to `value`. pub(crate) unsafe fn get_bytes_raw(self, key: *const u8, value: *mut u8) { let key = read_bytes32(key); @@ -34,10 +34,18 @@ impl Context { write_bytes32(value, self.get_bytes(&key)); } - /// Set the value at `key` in storage to `value`. - pub(crate) fn set_bytes(self, key: Bytes32, value: Bytes32) { - let mut storage = STORAGE.entry(self.thread_name).or_default(); - storage.contract_data.insert(key, value); + /// Get the value at `key` in storage. + fn get_bytes(self, key: &Bytes32) -> Bytes32 { + let storage = self.get_storage(); + let msg_receiver = + storage.msg_receiver.expect("msg_receiver should be set"); + storage + .contract_data + .get(&msg_receiver) + .expect("contract receiver should have a storage initialised") + .get(key) + .copied() + .unwrap_or_default() } /// Set the raw value at `key` in storage to `value`. @@ -46,11 +54,205 @@ impl Context { self.set_bytes(key, value); } + /// Set the value at `key` in storage to `value`. + fn set_bytes(self, key: Bytes32, value: Bytes32) { + let mut storage = self.get_storage(); + let msg_receiver = + storage.msg_receiver.expect("msg_receiver should be set"); + storage + .contract_data + .get_mut(&msg_receiver) + .expect("contract receiver should have a storage initialised") + .insert(key, value); + } + /// Clears storage, removing all key-value pairs associated with the current /// test thread. pub fn reset_storage(self) { STORAGE.remove(&self.thread_name); } + + /// Set the message sender account address. + fn set_msg_sender(&self, msg_sender: Address) -> Option
{ + self.get_storage().msg_sender.replace(msg_sender) + } + + /// Get the message sender account address. + pub fn get_msg_sender(&self) -> Option
{ + self.get_storage().msg_sender + } + + /// Set the address of the contract, that should be called. + fn set_msg_receiver(&self, msg_receiver: Address) -> Option
{ + self.get_storage().msg_receiver.replace(msg_receiver) + } + + /// Get the address of the contract, that should be called. + fn get_msg_receiver(&self) -> Option
{ + self.get_storage().msg_receiver + } + + /// Initialise contract storage for the current test thread and + /// `contract_address`. + fn init_contract( + self, + contract_address: Address, + ) { + if STORAGE + .entry(self.thread_name.clone()) + .or_default() + .contract_data + .insert(contract_address, HashMap::new()) + .is_some() + { + panic!("contract storage already initialized - contract_address: {contract_address}"); + } + + if CALL_STORAGE + .entry(self.thread_name) + .or_default() + .contract_router + .insert( + contract_address, + Mutex::new(Box::new(unsafe { ST::new(uint!(0_U256), 0) })), + ) + .is_some() + { + panic!("contract storage already initialized - contract_address: {contract_address}"); + } + } + + /// Call the contract at raw `address` with the given raw `calldata`. + pub(crate) unsafe fn call_contract_raw( + self, + address: *const u8, + calldata: *const u8, + calldata_len: usize, + return_data_len: *mut usize, + ) -> u8 { + let address_bytes = slice::from_raw_parts(address, 20); + let address = Address::from_slice(address_bytes); + + let input = slice::from_raw_parts(calldata, calldata_len); + let selector = + u32::from_be_bytes(TryInto::try_into(&input[..4]).unwrap()); + + match self.call_contract(address, selector, &input[4..]) { + Ok(res) => { + return_data_len.write(res.len()); + self.set_return_data(res); + 0 + } + Err(err) => { + return_data_len.write(err.len()); + self.set_return_data(err); + 1 + } + } + } + + fn call_contract( + &self, + contract_address: Address, + selector: u32, + input: &[u8], + ) -> ArbResult { + // Set the current contract as message sender and callee contract as + // receiver. + let previous_receiver = self + .set_msg_receiver(contract_address) + .expect("msg_receiver should be set"); + let previous_msg_sender = self + .set_msg_sender(previous_receiver) + .expect("msg_sender should be set"); + + // Call external contract. + let call_storage = self.get_call_storage(); + let router = call_storage + .contract_router + .get(&contract_address) + .expect("contract router should be set"); + let mut router = router.lock().expect("should lock test router"); + let result = router.route(selector, input).unwrap_or_else(|| { + panic!("selector not found - selector: {selector}") + }); + + // Set the previous message sender and receiver back. + let _ = self.set_msg_receiver(previous_receiver); + let _ = self.set_msg_sender(previous_msg_sender); + + result + } + + fn set_return_data(&self, data: Vec) { + let mut call_storage = self.get_call_storage(); + let _ = call_storage.call_output_len.insert(data.len()); + let _ = call_storage.call_output.insert(data); + } + + pub(crate) unsafe fn read_return_data_raw( + self, + dest: *mut u8, + size: usize, + ) -> usize { + let data = self.get_return_data(); + ptr::copy(data.as_ptr(), dest, size); + data.len() + } + + pub(crate) fn get_return_data_size(&self) -> usize { + self.get_call_storage() + .call_output_len + .take() + .expect("call_output_len should be set") + } + + fn get_return_data(&self) -> Vec { + self.get_call_storage() + .call_output + .take() + .expect("call_output should be set") + } + + /// Check if the contract at raw `address` has code. + pub(crate) unsafe fn has_code_raw(self, address: *const u8) -> bool { + let address_bytes = slice::from_raw_parts(address, 20); + let address = Address::from_slice(address_bytes); + self.has_code(address) + } + + /// Check if the contract at `address` has code. + #[must_use] + fn has_code(&self, address: Address) -> bool { + let call_storage = self.get_call_storage(); + call_storage.contract_router.contains_key(&address) + } + + /// Get reference to the storage for the current test thread. + fn get_storage(&self) -> RefMut<'static, ThreadName, MockStorage> { + STORAGE + .get_mut(&self.thread_name) + .expect("contract should be initialised first") + } + + /// Get reference to the call storage for the current test thread. + fn get_call_storage(&self) -> RefMut<'static, ThreadName, CallStorage> { + CALL_STORAGE + .get_mut(&self.thread_name.clone()) + .expect("contract should be initialised first") + } +} + +/// Read the word from location pointed by `ptr`. +unsafe fn read_bytes32(ptr: *const u8) -> Bytes32 { + let mut res = Bytes32::default(); + ptr::copy(ptr, res.as_mut_ptr(), WORD_BYTES); + res +} + +/// Write the word `bytes` to the location pointed by `ptr`. +unsafe fn write_bytes32(ptr: *mut u8, bytes: Bytes32) { + ptr::copy(bytes.as_ptr(), ptr, WORD_BYTES); } /// Storage mock: A global mutable key-value store. @@ -79,31 +281,136 @@ impl ThreadName { /// Storage for unit test's mock data. #[derive(Default)] struct MockStorage { - /// Contract's mock data storage. - contract_data: HashMap, + /// Address of the message sender. + msg_sender: Option
, + /// Address of the contract that is currently receiving the message. + msg_receiver: Option
, + /// Contract's address to mock data storage mapping. + contract_data: HashMap, } -/// Read the word from location pointed by `ptr`. -unsafe fn read_bytes32(ptr: *const u8) -> Bytes32 { - let mut res = Bytes32::default(); - ptr::copy(ptr, res.as_mut_ptr(), WORD_BYTES); - res +type ContractStorage = HashMap; + +/// The key is the name of the test thread, and the value is external call +/// metadata. +static CALL_STORAGE: Lazy> = + Lazy::new(DashMap::new); + +/// Metadata related to call of external contract. +#[derive(Default)] +struct CallStorage { + // Contract's address to router mapping. + // NOTE: Mutex is important since contract type is not `Sync`. + contract_router: HashMap>>, + // Output of a contract call. + call_output: Option>, + // Output length of a contract call. + call_output_len: Option, } -/// Write the word `bytes` to the location pointed by `ptr`. -unsafe fn write_bytes32(ptr: *mut u8, bytes: Bytes32) { - ptr::copy(bytes.as_ptr(), ptr, WORD_BYTES); +/// A trait for routing messages to the appropriate selector in tests. +pub trait TestRouter: Send { + /// Tries to find and execute a method for the given selector, returning + /// `None` if none is found. + fn route(&mut self, selector: u32, input: &[u8]) -> Option; } -/// Initializes fields of contract storage and child contract storages with -/// default values. -pub trait DefaultStorage: StorageType { - /// Initializes fields of contract storage and child contract storages with - /// default values. - #[must_use] +impl TestRouter for R +where + R: Router + TopLevelStorage + BorrowMut + Send, +{ + fn route(&mut self, selector: u32, input: &[u8]) -> Option { + >::route(self, selector, input) + } +} + +impl Default for Contract { fn default() -> Self { - unsafe { Self::new(uint!(0_U256), 0) } + Contract::random() + } +} + +pub struct ContractCall { + contract: ST, + caller_address: Address, + contract_address: Address, +} + +impl ContractCall { + pub fn address(&self) -> Address { + self.contract_address + } +} + +impl ::core::ops::Deref for ContractCall { + type Target = ST; + + #[inline] + fn deref(&self) -> &Self::Target { + let _ = Context::current().set_msg_sender(self.caller_address); + let _ = Context::current().set_msg_receiver(self.contract_address); + &self.contract } } -impl DefaultStorage for ST {} +impl ::core::ops::DerefMut for ContractCall { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + let _ = Context::current().set_msg_sender(self.caller_address); + let _ = Context::current().set_msg_receiver(self.contract_address); + &mut self.contract + } +} + +pub struct Contract { + phantom: ::core::marker::PhantomData, + address: Address, +} + +impl Contract { + pub fn new(address: Address) -> Self { + Context::current().init_contract::(address); + + Self { phantom: ::core::marker::PhantomData, address } + } + + // TODO#q: probably we need generic initializer + + pub fn random() -> Self { + Self::new(Address::random()) + } + + pub fn address(&self) -> Address { + self.address + } + + pub fn sender(&self, account: Account) -> ContractCall { + ContractCall { + contract: unsafe { ST::new(uint!(0_U256), 0) }, + caller_address: account.address, + contract_address: self.address, + } + } +} + +#[derive(Clone, Copy)] +pub struct Account { + address: Address, +} + +impl Account { + #[must_use] + pub const fn new(address: Address) -> Self { + Self { address } + } + + #[must_use] + pub fn random() -> Self { + Self::new(Address::random()) + } + + #[must_use] + pub fn address(&self) -> Address { + self.address + } +} diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 7636b75..2efb30b 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -48,3 +48,106 @@ pub mod prelude; mod shims; pub use motsu_proc::test; + +#[cfg(all(test))] +mod tests { + #![deny(rustdoc::broken_intra_doc_links)] + extern crate alloc; + + use alloy_primitives::uint; + use stylus_sdk::{ + alloy_primitives::{Address, U256}, + call::Call, + msg, + prelude::{public, sol_storage, StorageType, TopLevelStorage}, + }; + + use crate::context::{Account, Contract}; + + sol_storage! { + pub struct PingContract { + uint256 _pings_count; + address _pinged_from; + } + } + + #[public] + impl PingContract { + fn ping(&mut self, to: Address, value: U256) -> Result> { + let receiver = IPongContract::new(to); + let call = Call::new_in(self); + let value = + receiver.pong(call, value).expect("should pong successfully"); + + let pings_count = self._pings_count.get(); + self._pings_count.set(pings_count + uint!(1_U256)); + self._pinged_from.set(msg::sender()); + Ok(value) + } + + fn ping_count(&self) -> U256 { + self._pings_count.get() + } + + fn pinged_from(&self) -> Address { + self._pinged_from.get() + } + } + + unsafe impl TopLevelStorage for PingContract {} + + stylus_sdk::stylus_proc::sol_interface! { + interface IPongContract { + #[allow(missing_docs)] + function pong(uint256 value) external returns (uint256); + } + } + + sol_storage! { + pub struct PongContract { + uint256 _pongs_count; + address _ponged_from; + } + } + + #[public] + impl PongContract { + pub fn pong(&mut self, value: U256) -> Result> { + let pongs_count = self._pongs_count.get(); + self._pongs_count.set(pongs_count + uint!(1_U256)); + self._ponged_from.set(msg::sender()); + Ok(value + uint!(1_U256)) + } + + fn pong_count(&self) -> U256 { + self._pongs_count.get() + } + + fn ponged_from(&self) -> Address { + self._ponged_from.get() + } + } + + unsafe impl TopLevelStorage for PongContract {} + + #[test] + fn ping_pong_works() { + let mut ping = Contract::::default(); + let mut pong = Contract::::default(); + + let alice = Account::random(); + + let value = uint!(10_U256); + let ponged_value = ping + .sender(alice) + .ping(pong.address(), value) + .expect("should ping successfully"); + + assert_eq!(ponged_value, value + uint!(1_U256)); + assert_eq!(ping.sender(alice).ping_count(), uint!(1_U256)); + assert_eq!(pong.sender(alice).pong_count(), uint!(1_U256)); + + assert_eq!(ping.sender(alice).pinged_from(), alice.address()); + assert_eq!(pong.sender(alice).ponged_from(), ping.address()); + } +} diff --git a/crates/motsu/src/prelude.rs b/crates/motsu/src/prelude.rs index 5c87982..a881a05 100644 --- a/crates/motsu/src/prelude.rs +++ b/crates/motsu/src/prelude.rs @@ -1,5 +1,5 @@ //! Common imports for `motsu` tests. pub use crate::{ - context::{Context, DefaultStorage}, + context::{Account, Context, Contract, ContractCall}, shims::*, }; diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index 162f9e2..a83afc4 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -126,7 +126,7 @@ pub const CONTRACT_ADDRESS: &[u8; 42] = /// Arbitrum's CHAID ID. pub const CHAIN_ID: u64 = 42161; -/// Externally Owned Account (EOA) code hash. +/// Externally Owned Account (EOA) code hash (wallet account). pub const EOA_CODEHASH: &[u8; 66] = b"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; @@ -134,6 +134,14 @@ pub const EOA_CODEHASH: &[u8; 66] = /// /// For normal L2-to-L2 transactions the semantics are equivalent to that of the /// EVM's [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. +/// Contract Account (CA) code hash (smart contract code). +/// NOTE: can be any 256-bit value to pass `has_code` check. +pub const CA_CODEHASH: &[u8; 66] = + b"0x1111111111111111111111111111111111111111111111111111111111111111"; + +/// Gets the address of the account that called the program. For normal +/// L2-to-L2 transactions the semantics are equivalent to that of the EVM's +/// [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. /// /// For L1-to-L2 retryable ticket transactions, the top-level sender's address /// will be aliased. See [`Retryable Ticket Address Aliasing`][aliasing] for @@ -148,8 +156,16 @@ pub const EOA_CODEHASH: &[u8; 66] = /// May panic if fails to parse `MSG_SENDER` as an address. #[no_mangle] pub unsafe extern "C" fn msg_sender(sender: *mut u8) { - let addr = const_hex::const_decode_to_array::<20>(MSG_SENDER).unwrap(); - std::ptr::copy(addr.as_ptr(), sender, 20); + let msg_sender = + Context::current().get_msg_sender().expect("msg_sender should be set"); + std::ptr::copy(msg_sender.as_ptr(), sender, 20); +} + +/// Get the ETH value (U256) in wei sent to the program. +#[no_mangle] +pub unsafe extern "C" fn msg_value(value: *mut u8) { + let dummy_msg_value: Bytes32 = Bytes32::default(); + std::ptr::copy(dummy_msg_value.as_ptr(), value, 32); } /// Gets the address of the current program. The semantics are equivalent to @@ -206,9 +222,15 @@ pub unsafe extern "C" fn emit_log(_: *const u8, _: usize, _: usize) { /// /// May panic if fails to parse `ACCOUNT_CODEHASH` as a keccack hash. #[no_mangle] -pub unsafe extern "C" fn account_codehash(_address: *const u8, dest: *mut u8) { +pub unsafe extern "C" fn account_codehash(address: *const u8, dest: *mut u8) { + let code_hash = if Context::current().has_code_raw(address) { + CA_CODEHASH + } else { + EOA_CODEHASH + }; + let account_codehash = - const_hex::const_decode_to_array::<32>(EOA_CODEHASH).unwrap(); + const_hex::const_decode_to_array::<32>(code_hash).unwrap(); std::ptr::copy(account_codehash.as_ptr(), dest, 32); } @@ -222,10 +244,7 @@ pub unsafe extern "C" fn account_codehash(_address: *const u8, dest: *mut u8) { /// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d #[no_mangle] pub unsafe extern "C" fn return_data_size() -> usize { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 + Context::current().get_return_data_size() } /// Copies the bytes of the last EVM call or deployment return result. @@ -243,10 +262,7 @@ pub unsafe extern "C" fn read_return_data( _offset: usize, _size: usize, ) -> usize { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 + Context::current().read_return_data_raw(_dest, _size) } /// Calls the contract at the given address with options for passing value and @@ -272,10 +288,12 @@ pub unsafe extern "C" fn call_contract( _gas: u64, _return_data_len: *mut usize, ) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 + Context::current().call_contract_raw( + _contract, + _calldata, + _calldata_len, + _return_data_len, + ) } /// Static calls the contract at the given address, with the option to limit the From feb635a55d54463aa51d1c0a81d20903a6cbe99d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 23 Dec 2024 22:45:07 +0400 Subject: [PATCH 02/42] bump stylus sdk to 0.7.0-rc.1 --- Cargo.lock | 8 ++++---- Cargo.toml | 13 +++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac059bf..c3c1871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,8 +1441,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stylus-proc" -version = "0.7.0-beta.1" -source = "git+https://github.com/qalisander/stylus-sdk-rs?branch=fix-encoding-in-sol-interface#3025bcb2a803ad08dc0c077dfc81465a54d9fb29" +version = "0.7.0-rc.1" +source = "git+https://github.com/OffchainLabs/stylus-sdk-rs?branch=rel/0.7.0-rc.1#754c69be04c0017a1d2573f2aa5b0c5f2483288a" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -1461,8 +1461,8 @@ dependencies = [ [[package]] name = "stylus-sdk" -version = "0.7.0-beta.1" -source = "git+https://github.com/qalisander/stylus-sdk-rs?branch=fix-encoding-in-sol-interface#3025bcb2a803ad08dc0c077dfc81465a54d9fb29" +version = "0.7.0-rc.1" +source = "git+https://github.com/OffchainLabs/stylus-sdk-rs?branch=rel/0.7.0-rc.1#754c69be04c0017a1d2573f2aa5b0c5f2483288a" dependencies = [ "alloy-primitives", "alloy-sol-types", diff --git a/Cargo.toml b/Cargo.toml index 65c47aa..9bfcf86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ all = "warn" const-hex = { version = "1.11.1", default-features = false } once_cell = "1.19.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } -stylus-sdk = { version = "0.7.0-beta.1", default-features = false } +stylus-sdk = { version = "0.7.0-rc.1", default-features = false } dashmap = "6.1.0" syn = { version = "2.0.58", features = ["full"] } proc-macro2 = "1.0.79" @@ -57,12 +57,9 @@ default = { extend-ignore-identifiers-re = [ ## TODO#q: remove this once the fix is released [patch.crates-io.stylus-sdk] -git = "https://github.com/qalisander/stylus-sdk-rs" -branch = "fix-encoding-in-sol-interface" +git = "https://github.com/OffchainLabs/stylus-sdk-rs" +branch = "rel/0.7.0-rc.1" [patch.crates-io.stylus-proc] -git = "https://github.com/qalisander/stylus-sdk-rs" -branch = "fix-encoding-in-sol-interface" - - - +git = "https://github.com/OffchainLabs/stylus-sdk-rs" +branch = "rel/0.7.0-rc.1" \ No newline at end of file From 270e2bedea1da383d4d1ab030a470ba5677cb173 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 24 Dec 2024 18:48:52 +0400 Subject: [PATCH 03/42] add docs --- crates/motsu/src/context.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 790517a..544b622 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -105,7 +105,7 @@ impl Context { .insert(contract_address, HashMap::new()) .is_some() { - panic!("contract storage already initialized - contract_address: {contract_address}"); + panic!("contract storage already initialized - contract_address is {contract_address}"); } if CALL_STORAGE @@ -118,7 +118,7 @@ impl Context { ) .is_some() { - panic!("contract storage already initialized - contract_address: {contract_address}"); + panic!("contract storage already initialized - contract_address is {contract_address}"); } } @@ -174,7 +174,7 @@ impl Context { .expect("contract router should be set"); let mut router = router.lock().expect("should lock test router"); let result = router.route(selector, input).unwrap_or_else(|| { - panic!("selector not found - selector: {selector}") + panic!("selector not found - selector is {selector}") }); // Set the previous message sender and receiver back. @@ -296,7 +296,7 @@ type ContractStorage = HashMap; static CALL_STORAGE: Lazy> = Lazy::new(DashMap::new); -/// Metadata related to call of external contract. +/// Metadata related to call of an external contract. #[derive(Default)] struct CallStorage { // Contract's address to router mapping. @@ -330,6 +330,8 @@ impl Default for Contract { } } +/// Contract call entity, related to the contract type `ST` and the caller's +/// account. pub struct ContractCall { contract: ST, caller_address: Address, @@ -362,12 +364,14 @@ impl ::core::ops::DerefMut for ContractCall { } } +/// Contract deployed in the test environment. pub struct Contract { phantom: ::core::marker::PhantomData, address: Address, } impl Contract { + /// Create a new contract with the given `address`. pub fn new(address: Address) -> Self { Context::current().init_contract::(address); @@ -376,14 +380,17 @@ impl Contract { // TODO#q: probably we need generic initializer + /// Create a new contract with random address. pub fn random() -> Self { Self::new(Address::random()) } + /// Get contract's test address. pub fn address(&self) -> Address { self.address } + /// Call contract `self` with `account` as a sender. pub fn sender(&self, account: Account) -> ContractCall { ContractCall { contract: unsafe { ST::new(uint!(0_U256), 0) }, @@ -393,22 +400,26 @@ impl Contract { } } +/// Account used to call contracts. #[derive(Clone, Copy)] pub struct Account { address: Address, } impl Account { + /// Create a new account with the given `address`. #[must_use] pub const fn new(address: Address) -> Self { Self { address } } + /// Create a new account with random address. #[must_use] pub fn random() -> Self { Self::new(Address::random()) } + /// Get account's address. #[must_use] pub fn address(&self) -> Address { self.address From 09e7a1a5214e82697495c8b6e0ebe1ca384a4911 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 00:12:54 +0400 Subject: [PATCH 04/42] ++ --- crates/motsu/src/context.rs | 5 +++++ crates/motsu/src/lib.rs | 34 ++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 544b622..4bb70a3 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -291,6 +291,10 @@ struct MockStorage { type ContractStorage = HashMap; +// TODO#q: use thread id, instead of thread name +// TODO#q: use composite key, like: (ThreadId, Address) +// TODO#q: move to call_context module + /// The key is the name of the test thread, and the value is external call /// metadata. static CALL_STORAGE: Lazy> = @@ -349,6 +353,7 @@ impl ::core::ops::Deref for ContractCall { #[inline] fn deref(&self) -> &Self::Target { + // TODO#q: move to separate function let _ = Context::current().set_msg_sender(self.caller_address); let _ = Context::current().set_msg_receiver(self.contract_address); &self.contract diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 2efb30b..6cc34ec 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -19,11 +19,14 @@ //! ```rust //! #[cfg(test)] //! mod tests { -//! use contracts::token::erc20::Erc20; +//! use openzeppelin_stylus::token::erc20::Erc20; +//! use motsu::prelude::{Account, Contract}; +//! use stylus_sdk::alloy_primitives::{Address, U256}; //! //! #[motsu::test] -//! fn reads_balance(contract: Erc20) { -//! let balance = contract.balance_of(Address::ZERO); // Access storage. +//! fn reads_balance(contract: Contract) { +//! let alice = Account::random(); +//! let balance = contract.sender(alice).balance_of(Address::ZERO); // Access storage. //! assert_eq!(balance, U256::ZERO); //! } //! } @@ -54,21 +57,21 @@ mod tests { #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; + use crate::context::{Account, Contract}; use alloy_primitives::uint; + use stylus_sdk::prelude::storage; + use stylus_sdk::storage::{StorageAddress, StorageU256}; use stylus_sdk::{ alloy_primitives::{Address, U256}, call::Call, msg, - prelude::{public, sol_storage, StorageType, TopLevelStorage}, + prelude::{public, StorageType, TopLevelStorage}, }; - use crate::context::{Account, Contract}; - - sol_storage! { - pub struct PingContract { - uint256 _pings_count; - address _pinged_from; - } + #[storage] + pub struct PingContract { + pub _pings_count: StorageU256, + pub _pinged_from: StorageAddress, } #[public] @@ -103,11 +106,10 @@ mod tests { } } - sol_storage! { - pub struct PongContract { - uint256 _pongs_count; - address _ponged_from; - } + #[storage] + pub struct PongContract { + pub _pongs_count: StorageU256, + pub _ponged_from: StorageAddress, } #[public] From f29671f335297a123adbddf09d5150e0f5f7320a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 01:25:54 +0400 Subject: [PATCH 05/42] ++ --- crates/motsu/src/context.rs | 10 +++++- crates/motsu/src/lib.rs | 63 ++++++++++++++++++++++++++++++++++--- crates/motsu/src/shims.rs | 1 + 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 4bb70a3..97e49a8 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -383,7 +383,15 @@ impl Contract { Self { phantom: ::core::marker::PhantomData, address } } - // TODO#q: probably we need generic initializer + /// Initialize the contract with an `initializer` function, and on behalf of + /// the given `account`. + pub fn init( + &self, + account: Account, + initializer: impl FnOnce(ContractCall), + ) { + initializer(self.sender(account)); + } /// Create a new contract with random address. pub fn random() -> Self { diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 6cc34ec..d828caf 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -57,17 +57,17 @@ mod tests { #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; - use crate::context::{Account, Contract}; use alloy_primitives::uint; - use stylus_sdk::prelude::storage; - use stylus_sdk::storage::{StorageAddress, StorageU256}; use stylus_sdk::{ alloy_primitives::{Address, U256}, call::Call, msg, - prelude::{public, StorageType, TopLevelStorage}, + prelude::{public, storage, StorageType, TopLevelStorage}, + storage::{StorageAddress, StorageU256}, }; + use crate::context::{Account, Contract}; + #[storage] pub struct PingContract { pub _pings_count: StorageU256, @@ -152,4 +152,59 @@ mod tests { assert_eq!(ping.sender(alice).pinged_from(), alice.address()); assert_eq!(pong.sender(alice).ponged_from(), ping.address()); } + + stylus_sdk::stylus_proc::sol_interface! { + interface IProxy { + #[allow(missing_docs)] + function callProxy(uint256 value) external returns (uint256); + } + } + + #[storage] + pub struct Proxy { + pub _next_proxy: StorageAddress, + } + + #[public] + impl Proxy { + pub fn call_proxy(&mut self, value: U256) -> U256 { + let next_proxy = self._next_proxy.get(); + + // If there is no next proxy, return the value. + if next_proxy.is_zero() { + value + } else { + // Otherwise, call the next proxy. + let proxy = IProxy::new(next_proxy); + let call = Call::new_in(self); + proxy.call_proxy(call, value).expect("should call proxy") + } + } + } + + unsafe impl TopLevelStorage for Proxy {} + + #[test] + fn test_three_proxies() { + let mut proxy1 = Contract::::default(); + let mut proxy2 = Contract::::default(); + let mut proxy3 = Contract::::default(); + + let alice = Account::random(); + + proxy1.init(alice, |mut proxy| { + proxy._next_proxy.set(proxy2.address()); + }); + proxy2.init(alice, |mut proxy| { + proxy._next_proxy.set(proxy3.address()); + }); + proxy3.init(alice, |mut proxy| { + proxy._next_proxy.set(Address::ZERO); + }); + + let value = uint!(10_U256); + let result = proxy1.sender(alice).call_proxy(value); + + assert_eq!(result, value); + } } diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index a83afc4..c1ad762 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -178,6 +178,7 @@ pub unsafe extern "C" fn msg_value(value: *mut u8) { /// May panic if fails to parse `CONTRACT_ADDRESS` as an address. #[no_mangle] pub unsafe extern "C" fn contract_address(address: *mut u8) { + // TODO#q: mock contract address. Rename msg_receiver -> contract_address. let addr = const_hex::const_decode_to_array::<20>(CONTRACT_ADDRESS).unwrap(); std::ptr::copy(addr.as_ptr(), address, 20); From 4f06a587bc66bc2df5dafb8a1f97d3a348272e96 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 01:33:49 +0400 Subject: [PATCH 06/42] ++ --- crates/motsu/src/context.rs | 4 ++-- crates/motsu/src/lib.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 97e49a8..416c30d 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -388,9 +388,9 @@ impl Contract { pub fn init( &self, account: Account, - initializer: impl FnOnce(ContractCall), + initializer: impl FnOnce(&mut ST), ) { - initializer(self.sender(account)); + initializer(&mut self.sender(account)); } /// Create a new contract with random address. diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index d828caf..abc51f4 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -192,14 +192,14 @@ mod tests { let alice = Account::random(); - proxy1.init(alice, |mut proxy| { - proxy._next_proxy.set(proxy2.address()); + proxy1.init(alice, |storage| { + storage._next_proxy.set(proxy2.address()); }); - proxy2.init(alice, |mut proxy| { - proxy._next_proxy.set(proxy3.address()); + proxy2.init(alice, |storage| { + storage._next_proxy.set(proxy3.address()); }); - proxy3.init(alice, |mut proxy| { - proxy._next_proxy.set(Address::ZERO); + proxy3.init(alice, |storage| { + storage._next_proxy.set(Address::ZERO); }); let value = uint!(10_U256); From 1799ae34d3303cae6ad403fd03dff3a9e2de41f5 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 01:42:09 +0400 Subject: [PATCH 07/42] use thread id --- crates/motsu/src/context.rs | 58 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 416c30d..23f6b6d 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -1,6 +1,9 @@ //! Unit-testing context for Stylus contracts. -use std::{borrow::BorrowMut, collections::HashMap, ptr, slice, sync::Mutex}; +use std::{ + borrow::BorrowMut, collections::HashMap, ptr, slice, sync::Mutex, + thread::ThreadId, +}; use alloy_primitives::Address; use dashmap::{mapref::one::RefMut, DashMap}; @@ -17,14 +20,22 @@ use crate::prelude::{Bytes32, WORD_BYTES}; /// Context of stylus unit tests associated with the current test thread. #[allow(clippy::module_name_repetitions)] pub struct Context { - thread_name: ThreadName, + thread: std::thread::Thread, } impl Context { /// Get test context associated with the current test thread. #[must_use] pub fn current() -> Self { - Self { thread_name: ThreadName::current() } + Self { thread: std::thread::current() } + } + + /// Get the name of the current test thread. + pub fn thread_name(&self) -> String { + self.thread + .name() + .expect("should retrieve current thread name") + .to_string() } /// Get the raw value at `key` in storage and write it to `value`. @@ -69,7 +80,7 @@ impl Context { /// Clears storage, removing all key-value pairs associated with the current /// test thread. pub fn reset_storage(self) { - STORAGE.remove(&self.thread_name); + STORAGE.remove(&self.thread.id()); } /// Set the message sender account address. @@ -99,7 +110,7 @@ impl Context { contract_address: Address, ) { if STORAGE - .entry(self.thread_name.clone()) + .entry(self.thread.id()) .or_default() .contract_data .insert(contract_address, HashMap::new()) @@ -109,7 +120,7 @@ impl Context { } if CALL_STORAGE - .entry(self.thread_name) + .entry(self.thread.id()) .or_default() .contract_router .insert( @@ -229,16 +240,16 @@ impl Context { } /// Get reference to the storage for the current test thread. - fn get_storage(&self) -> RefMut<'static, ThreadName, MockStorage> { + fn get_storage(&self) -> RefMut<'static, ThreadId, MockStorage> { STORAGE - .get_mut(&self.thread_name) + .get_mut(&self.thread.id()) .expect("contract should be initialised first") } /// Get reference to the call storage for the current test thread. - fn get_call_storage(&self) -> RefMut<'static, ThreadName, CallStorage> { + fn get_call_storage(&self) -> RefMut<'static, ThreadId, CallStorage> { CALL_STORAGE - .get_mut(&self.thread_name.clone()) + .get_mut(&self.thread.id()) .expect("contract should be initialised first") } } @@ -260,23 +271,7 @@ unsafe fn write_bytes32(ptr: *mut u8, bytes: Bytes32) { /// /// The key is the name of the test thread, and the value is the storage of the /// test case. -static STORAGE: Lazy> = - Lazy::new(DashMap::new); - -/// Test thread name metadata. -#[derive(Clone, Eq, PartialEq, Hash)] -struct ThreadName(String); - -impl ThreadName { - /// Get the name of the current test thread. - fn current() -> Self { - let current_thread_name = std::thread::current() - .name() - .expect("should retrieve current thread name") - .to_string(); - Self(current_thread_name) - } -} +static STORAGE: Lazy> = Lazy::new(DashMap::new); /// Storage for unit test's mock data. #[derive(Default)] @@ -291,13 +286,12 @@ struct MockStorage { type ContractStorage = HashMap; -// TODO#q: use thread id, instead of thread name // TODO#q: use composite key, like: (ThreadId, Address) // TODO#q: move to call_context module /// The key is the name of the test thread, and the value is external call /// metadata. -static CALL_STORAGE: Lazy> = +static CALL_STORAGE: Lazy> = Lazy::new(DashMap::new); /// Metadata related to call of an external contract. @@ -385,11 +379,7 @@ impl Contract { /// Initialize the contract with an `initializer` function, and on behalf of /// the given `account`. - pub fn init( - &self, - account: Account, - initializer: impl FnOnce(&mut ST), - ) { + pub fn init(&self, account: Account, initializer: impl FnOnce(&mut ST)) { initializer(&mut self.sender(account)); } From 32d59b5a0a69aabccc43e1127d3c47db5cdce129 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 01:50:34 +0400 Subject: [PATCH 08/42] ++ --- crates/motsu/src/context.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 23f6b6d..3ba5c47 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -337,9 +337,16 @@ pub struct ContractCall { } impl ContractCall { + /// Get the contract's address. pub fn address(&self) -> Address { self.contract_address } + + /// Preset the call parameters. + fn set_call_params(&self) { + let _ = Context::current().set_msg_sender(self.caller_address); + let _ = Context::current().set_msg_receiver(self.contract_address); + } } impl ::core::ops::Deref for ContractCall { @@ -347,9 +354,7 @@ impl ::core::ops::Deref for ContractCall { #[inline] fn deref(&self) -> &Self::Target { - // TODO#q: move to separate function - let _ = Context::current().set_msg_sender(self.caller_address); - let _ = Context::current().set_msg_receiver(self.contract_address); + self.set_call_params(); &self.contract } } @@ -357,8 +362,7 @@ impl ::core::ops::Deref for ContractCall { impl ::core::ops::DerefMut for ContractCall { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - let _ = Context::current().set_msg_sender(self.caller_address); - let _ = Context::current().set_msg_receiver(self.contract_address); + self.set_call_params(); &mut self.contract } } From 6947aa63d441c0875b06e0b9337410be9cd56c36 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 02:53:06 +0400 Subject: [PATCH 09/42] test three_proxies works --- crates/motsu/src/call.rs | 0 crates/motsu/src/context.rs | 89 +++++++++++++++++++++---------------- crates/motsu/src/lib.rs | 17 ++++--- 3 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 crates/motsu/src/call.rs diff --git a/crates/motsu/src/call.rs b/crates/motsu/src/call.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 3ba5c47..e044250 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -47,7 +47,7 @@ impl Context { /// Get the value at `key` in storage. fn get_bytes(self, key: &Bytes32) -> Bytes32 { - let storage = self.get_storage(); + let storage = self.storage(); let msg_receiver = storage.msg_receiver.expect("msg_receiver should be set"); storage @@ -67,7 +67,7 @@ impl Context { /// Set the value at `key` in storage to `value`. fn set_bytes(self, key: Bytes32, value: Bytes32) { - let mut storage = self.get_storage(); + let mut storage = self.storage(); let msg_receiver = storage.msg_receiver.expect("msg_receiver should be set"); storage @@ -85,22 +85,22 @@ impl Context { /// Set the message sender account address. fn set_msg_sender(&self, msg_sender: Address) -> Option
{ - self.get_storage().msg_sender.replace(msg_sender) + self.storage().msg_sender.replace(msg_sender) } /// Get the message sender account address. pub fn get_msg_sender(&self) -> Option
{ - self.get_storage().msg_sender + self.storage().msg_sender } /// Set the address of the contract, that should be called. fn set_msg_receiver(&self, msg_receiver: Address) -> Option
{ - self.get_storage().msg_receiver.replace(msg_receiver) + self.storage().msg_receiver.replace(msg_receiver) } /// Get the address of the contract, that should be called. fn get_msg_receiver(&self) -> Option
{ - self.get_storage().msg_receiver + self.storage().msg_receiver } /// Initialise contract storage for the current test thread and @@ -120,12 +120,13 @@ impl Context { } if CALL_STORAGE - .entry(self.thread.id()) - .or_default() - .contract_router .insert( - contract_address, - Mutex::new(Box::new(unsafe { ST::new(uint!(0_U256), 0) })), + (self.thread.id(), contract_address), + CallStorage { + router: Mutex::new(Box::new(unsafe { + ST::new(uint!(0_U256), 0) + })), + }, ) .is_some() { @@ -178,11 +179,7 @@ impl Context { .expect("msg_sender should be set"); // Call external contract. - let call_storage = self.get_call_storage(); - let router = call_storage - .contract_router - .get(&contract_address) - .expect("contract router should be set"); + let router = &self.call_context(contract_address).storage().router; let mut router = router.lock().expect("should lock test router"); let result = router.route(selector, input).unwrap_or_else(|| { panic!("selector not found - selector is {selector}") @@ -196,7 +193,7 @@ impl Context { } fn set_return_data(&self, data: Vec) { - let mut call_storage = self.get_call_storage(); + let mut call_storage = self.storage(); let _ = call_storage.call_output_len.insert(data.len()); let _ = call_storage.call_output.insert(data); } @@ -212,17 +209,14 @@ impl Context { } pub(crate) fn get_return_data_size(&self) -> usize { - self.get_call_storage() + self.storage() .call_output_len .take() .expect("call_output_len should be set") } fn get_return_data(&self) -> Vec { - self.get_call_storage() - .call_output - .take() - .expect("call_output should be set") + self.storage().call_output.take().expect("call_output should be set") } /// Check if the contract at raw `address` has code. @@ -235,22 +229,18 @@ impl Context { /// Check if the contract at `address` has code. #[must_use] fn has_code(&self, address: Address) -> bool { - let call_storage = self.get_call_storage(); - call_storage.contract_router.contains_key(&address) + self.call_context(address).exists() } /// Get reference to the storage for the current test thread. - fn get_storage(&self) -> RefMut<'static, ThreadId, MockStorage> { + fn storage(&self) -> RefMut<'static, ThreadId, MockStorage> { STORAGE .get_mut(&self.thread.id()) .expect("contract should be initialised first") } - /// Get reference to the call storage for the current test thread. - fn get_call_storage(&self) -> RefMut<'static, ThreadId, CallStorage> { - CALL_STORAGE - .get_mut(&self.thread.id()) - .expect("contract should be initialised first") + fn call_context(&self, address: Address) -> CallContext { + CallContext { thread: self.thread.clone(), contract_address: address } } } @@ -282,6 +272,10 @@ struct MockStorage { msg_receiver: Option
, /// Contract's address to mock data storage mapping. contract_data: HashMap, + // Output of a contract call. + call_output: Option>, + // Output length of a contract call. + call_output_len: Option, } type ContractStorage = HashMap; @@ -289,21 +283,40 @@ type ContractStorage = HashMap; // TODO#q: use composite key, like: (ThreadId, Address) // TODO#q: move to call_context module +struct CallContext { + thread: std::thread::Thread, + contract_address: Address, +} + +impl CallContext { + /// Get reference to the call storage for the current test thread. + fn storage(&self) -> RefMut<'static, CallStorageKey, CallStorage> { + CALL_STORAGE + .get_mut(&self.storage_key()) + .expect("contract should be initialised first") + } + + fn exists(&self) -> bool { + CALL_STORAGE.contains_key(&self.storage_key()) + } + + fn storage_key(&self) -> CallStorageKey { + (self.thread.id(), self.contract_address) + } +} + +type CallStorageKey = (ThreadId, Address); + /// The key is the name of the test thread, and the value is external call /// metadata. -static CALL_STORAGE: Lazy> = +static CALL_STORAGE: Lazy> = Lazy::new(DashMap::new); /// Metadata related to call of an external contract. -#[derive(Default)] struct CallStorage { - // Contract's address to router mapping. + // Contract's router. // NOTE: Mutex is important since contract type is not `Sync`. - contract_router: HashMap>>, - // Output of a contract call. - call_output: Option>, - // Output length of a contract call. - call_output_len: Option, + router: Mutex>, } /// A trait for routing messages to the appropriate selector in tests. diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index abc51f4..1f0dcce 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -49,6 +49,7 @@ mod context; pub mod prelude; mod shims; +mod call; pub use motsu_proc::test; @@ -62,7 +63,7 @@ mod tests { alloy_primitives::{Address, U256}, call::Call, msg, - prelude::{public, storage, StorageType, TopLevelStorage}, + prelude::{public, storage, TopLevelStorage}, storage::{StorageAddress, StorageU256}, }; @@ -134,8 +135,8 @@ mod tests { #[test] fn ping_pong_works() { - let mut ping = Contract::::default(); - let mut pong = Contract::::default(); + let ping = Contract::::default(); + let pong = Contract::::default(); let alice = Account::random(); @@ -185,10 +186,10 @@ mod tests { unsafe impl TopLevelStorage for Proxy {} #[test] - fn test_three_proxies() { - let mut proxy1 = Contract::::default(); - let mut proxy2 = Contract::::default(); - let mut proxy3 = Contract::::default(); + fn three_proxies() { + let proxy1 = Contract::::default(); + let proxy2 = Contract::::default(); + let proxy3 = Contract::::default(); let alice = Account::random(); @@ -207,4 +208,6 @@ mod tests { assert_eq!(result, value); } + + // TODO#q: add has code test } From 7d6ddd2347ee07112e54ff921947e9ff0377780e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 03:17:34 +0400 Subject: [PATCH 10/42] ++ --- crates/motsu/src/context.rs | 94 +++++++++++++++---------- crates/motsu/src/lib.rs | 2 +- crates/motsu/src/{call.rs => router.rs} | 0 3 files changed, 58 insertions(+), 38 deletions(-) rename crates/motsu/src/{call.rs => router.rs} (100%) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index e044250..3f7fd56 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -105,7 +105,7 @@ impl Context { /// Initialise contract storage for the current test thread and /// `contract_address`. - fn init_contract( + fn init_storage( self, contract_address: Address, ) { @@ -119,19 +119,7 @@ impl Context { panic!("contract storage already initialized - contract_address is {contract_address}"); } - if CALL_STORAGE - .insert( - (self.thread.id(), contract_address), - CallStorage { - router: Mutex::new(Box::new(unsafe { - ST::new(uint!(0_U256), 0) - })), - }, - ) - .is_some() - { - panic!("contract storage already initialized - contract_address is {contract_address}"); - } + self.router_for(contract_address).init_storage::(); } /// Call the contract at raw `address` with the given raw `calldata`. @@ -179,11 +167,12 @@ impl Context { .expect("msg_sender should be set"); // Call external contract. - let router = &self.call_context(contract_address).storage().router; - let mut router = router.lock().expect("should lock test router"); - let result = router.route(selector, input).unwrap_or_else(|| { - panic!("selector not found - selector is {selector}") - }); + let result = self + .router_for(contract_address) + .route(selector, input) + .unwrap_or_else(|| { + panic!("selector not found - selector is {selector}") + }); // Set the previous message sender and receiver back. let _ = self.set_msg_receiver(previous_receiver); @@ -229,7 +218,7 @@ impl Context { /// Check if the contract at `address` has code. #[must_use] fn has_code(&self, address: Address) -> bool { - self.call_context(address).exists() + self.router_for(address).exists() } /// Get reference to the storage for the current test thread. @@ -239,8 +228,9 @@ impl Context { .expect("contract should be initialised first") } - fn call_context(&self, address: Address) -> CallContext { - CallContext { thread: self.thread.clone(), contract_address: address } + /// Get router for the contract at `address`. + fn router_for(&self, address: Address) -> RouterContext { + RouterContext { thread: self.thread.clone(), contract_address: address } } } @@ -280,40 +270,69 @@ struct MockStorage { type ContractStorage = HashMap; -// TODO#q: use composite key, like: (ThreadId, Address) // TODO#q: move to call_context module -struct CallContext { +struct RouterContext { thread: std::thread::Thread, contract_address: Address, } -impl CallContext { +impl RouterContext { /// Get reference to the call storage for the current test thread. - fn storage(&self) -> RefMut<'static, CallStorageKey, CallStorage> { - CALL_STORAGE + fn storage(&self) -> RefMut<'static, RouterStorageKey, RouterStorage> { + ROUTER_STORAGE .get_mut(&self.storage_key()) .expect("contract should be initialised first") } - fn exists(&self) -> bool { - CALL_STORAGE.contains_key(&self.storage_key()) + /// Check if the router exists for the contract. + pub(crate) fn exists(&self) -> bool { + ROUTER_STORAGE.contains_key(&self.storage_key()) } - fn storage_key(&self) -> CallStorageKey { + fn storage_key(&self) -> RouterStorageKey { (self.thread.id(), self.contract_address) } + + pub(crate) fn route( + &self, + selector: u32, + input: &[u8], + ) -> Option { + let router = &self.storage().router; + let mut router = router.lock().expect("should lock test router"); + router.route(selector, input) + } + + /// Initialise contract router for the current test thread and + /// `contract_address`. + fn init_storage(self) { + let contract_address = self.contract_address; + if ROUTER_STORAGE + .insert( + (self.thread.id(), contract_address), + RouterStorage { + router: Mutex::new(Box::new(unsafe { + ST::new(uint!(0_U256), 0) + })), + }, + ) + .is_some() + { + panic!("contract router is already initialized - contract_address is {contract_address}"); + } + } } -type CallStorageKey = (ThreadId, Address); +type RouterStorageKey = (ThreadId, Address); -/// The key is the name of the test thread, and the value is external call -/// metadata. -static CALL_STORAGE: Lazy> = +/// The key is the name of the test thread, and the value is contract's router +/// data. +static ROUTER_STORAGE: Lazy> = Lazy::new(DashMap::new); -/// Metadata related to call of an external contract. -struct CallStorage { +/// Metadata related to the router of an external contract. +struct RouterStorage { // Contract's router. // NOTE: Mutex is important since contract type is not `Sync`. router: Mutex>, @@ -388,8 +407,9 @@ pub struct Contract { impl Contract { /// Create a new contract with the given `address`. + #[must_use] pub fn new(address: Address) -> Self { - Context::current().init_contract::(address); + Context::current().init_storage::(address); Self { phantom: ::core::marker::PhantomData, address } } diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 1f0dcce..042f2f3 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -49,7 +49,7 @@ mod context; pub mod prelude; mod shims; -mod call; +mod router; pub use motsu_proc::test; diff --git a/crates/motsu/src/call.rs b/crates/motsu/src/router.rs similarity index 100% rename from crates/motsu/src/call.rs rename to crates/motsu/src/router.rs From 08c653079c6158168a4fa8a1ce74d8a42ea25f85 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 03:31:39 +0400 Subject: [PATCH 11/42] ++ --- crates/motsu/src/context.rs | 119 +++++------------------------------- crates/motsu/src/router.rs | 103 +++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 104 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 3f7fd56..808ad7c 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -1,21 +1,16 @@ //! Unit-testing context for Stylus contracts. -use std::{ - borrow::BorrowMut, collections::HashMap, ptr, slice, sync::Mutex, - thread::ThreadId, -}; +use std::{collections::HashMap, ptr, slice, thread::ThreadId}; use alloy_primitives::Address; use dashmap::{mapref::one::RefMut, DashMap}; use once_cell::sync::Lazy; -use stylus_sdk::{ - abi::Router, - alloy_primitives::uint, - prelude::{StorageType, TopLevelStorage}, - ArbResult, -}; +use stylus_sdk::{alloy_primitives::uint, prelude::StorageType, ArbResult}; -use crate::prelude::{Bytes32, WORD_BYTES}; +use crate::{ + prelude::{Bytes32, WORD_BYTES}, + router::{RouterContext, TestRouter}, +}; /// Context of stylus unit tests associated with the current test thread. #[allow(clippy::module_name_repetitions)] @@ -103,7 +98,7 @@ impl Context { self.storage().msg_receiver } - /// Initialise contract storage for the current test thread and + /// Initialise contract's storage for the current test thread and /// `contract_address`. fn init_storage( self, @@ -230,7 +225,7 @@ impl Context { /// Get router for the contract at `address`. fn router_for(&self, address: Address) -> RouterContext { - RouterContext { thread: self.thread.clone(), contract_address: address } + RouterContext::new(self.thread.clone(), address) } } @@ -270,96 +265,6 @@ struct MockStorage { type ContractStorage = HashMap; -// TODO#q: move to call_context module - -struct RouterContext { - thread: std::thread::Thread, - contract_address: Address, -} - -impl RouterContext { - /// Get reference to the call storage for the current test thread. - fn storage(&self) -> RefMut<'static, RouterStorageKey, RouterStorage> { - ROUTER_STORAGE - .get_mut(&self.storage_key()) - .expect("contract should be initialised first") - } - - /// Check if the router exists for the contract. - pub(crate) fn exists(&self) -> bool { - ROUTER_STORAGE.contains_key(&self.storage_key()) - } - - fn storage_key(&self) -> RouterStorageKey { - (self.thread.id(), self.contract_address) - } - - pub(crate) fn route( - &self, - selector: u32, - input: &[u8], - ) -> Option { - let router = &self.storage().router; - let mut router = router.lock().expect("should lock test router"); - router.route(selector, input) - } - - /// Initialise contract router for the current test thread and - /// `contract_address`. - fn init_storage(self) { - let contract_address = self.contract_address; - if ROUTER_STORAGE - .insert( - (self.thread.id(), contract_address), - RouterStorage { - router: Mutex::new(Box::new(unsafe { - ST::new(uint!(0_U256), 0) - })), - }, - ) - .is_some() - { - panic!("contract router is already initialized - contract_address is {contract_address}"); - } - } -} - -type RouterStorageKey = (ThreadId, Address); - -/// The key is the name of the test thread, and the value is contract's router -/// data. -static ROUTER_STORAGE: Lazy> = - Lazy::new(DashMap::new); - -/// Metadata related to the router of an external contract. -struct RouterStorage { - // Contract's router. - // NOTE: Mutex is important since contract type is not `Sync`. - router: Mutex>, -} - -/// A trait for routing messages to the appropriate selector in tests. -pub trait TestRouter: Send { - /// Tries to find and execute a method for the given selector, returning - /// `None` if none is found. - fn route(&mut self, selector: u32, input: &[u8]) -> Option; -} - -impl TestRouter for R -where - R: Router + TopLevelStorage + BorrowMut + Send, -{ - fn route(&mut self, selector: u32, input: &[u8]) -> Option { - >::route(self, selector, input) - } -} - -impl Default for Contract { - fn default() -> Self { - Contract::random() - } -} - /// Contract call entity, related to the contract type `ST` and the caller's /// account. pub struct ContractCall { @@ -405,9 +310,15 @@ pub struct Contract { address: Address, } +impl Default for Contract { + fn default() -> Self { + Contract::random() + } +} + impl Contract { /// Create a new contract with the given `address`. - #[must_use] + #[must_use] pub fn new(address: Address) -> Self { Context::current().init_storage::(address); diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index e69de29..97d14d9 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -0,0 +1,103 @@ +use std::{borrow::BorrowMut, sync::Mutex, thread::ThreadId}; + +use alloy_primitives::{uint, Address}; +use dashmap::{mapref::one::RefMut, DashMap}; +use once_cell::sync::Lazy; +use stylus_sdk::{ + abi::Router, + prelude::{StorageType, TopLevelStorage}, + ArbResult, +}; + +/// Context for the router of a test contract for current test thread and +/// contract's address. +pub(crate) struct RouterContext { + thread: std::thread::Thread, + contract_address: Address, +} + +impl RouterContext { + /// Create a new router context. + pub(crate) fn new( + thread: std::thread::Thread, + contract_address: Address, + ) -> Self { + Self { thread, contract_address } + } + + /// Get reference to the call storage for the current test thread. + fn storage(&self) -> RefMut<'static, RouterStorageKey, RouterStorage> { + ROUTER_STORAGE + .get_mut(&self.storage_key()) + .expect("contract should be initialised first") + } + + /// Check if the router exists for the contract. + pub(crate) fn exists(&self) -> bool { + ROUTER_STORAGE.contains_key(&self.storage_key()) + } + + fn storage_key(&self) -> RouterStorageKey { + (self.thread.id(), self.contract_address) + } + + pub(crate) fn route( + &self, + selector: u32, + input: &[u8], + ) -> Option { + let router = &self.storage().router; + let mut router = router.lock().expect("should lock test router"); + router.route(selector, input) + } + + /// Initialise contract router for the current test thread and + /// `contract_address`. + pub(crate) fn init_storage(self) { + let storage_key = self.storage_key(); + let contract_address = self.contract_address; + if ROUTER_STORAGE + .insert( + storage_key, + RouterStorage { + router: Mutex::new(Box::new(unsafe { + ST::new(uint!(0_U256), 0) + })), + }, + ) + .is_some() + { + panic!("contract's router is already initialized - contract_address is {contract_address}"); + } + } +} + +type RouterStorageKey = (ThreadId, Address); + +/// The key is the name of the test thread, and the value is contract's router +/// data. +static ROUTER_STORAGE: Lazy> = + Lazy::new(DashMap::new); + +/// Metadata related to the router of an external contract. +struct RouterStorage { + // Contract's router. + // NOTE: Mutex is important since contract type is not `Sync`. + router: Mutex>, +} + +/// A trait for routing messages to the appropriate selector in tests. +pub(crate) trait TestRouter: Send { + /// Tries to find and execute a method for the given selector, returning + /// `None` if none is found. + fn route(&mut self, selector: u32, input: &[u8]) -> Option; +} + +impl TestRouter for R +where + R: Router + TopLevelStorage + BorrowMut + Send, +{ + fn route(&mut self, selector: u32, input: &[u8]) -> Option { + >::route(self, selector, input) + } +} From 233e970cbfda247fff7e9e0ef7736b6c1c29c8b9 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 03:49:49 +0400 Subject: [PATCH 12/42] ++ --- crates/motsu/src/context.rs | 2 +- crates/motsu/src/lib.rs | 6 ++++-- crates/motsu/src/router.rs | 18 ++++++++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 808ad7c..8627fe6 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -244,7 +244,7 @@ unsafe fn write_bytes32(ptr: *mut u8, bytes: Bytes32) { /// Storage mock: A global mutable key-value store. /// Allows concurrent access. /// -/// The key is the name of the test thread, and the value is the storage of the +/// The key is an id of the test thread, and the value is the storage of the /// test case. static STORAGE: Lazy> = Lazy::new(DashMap::new); diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 042f2f3..f28a73e 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -154,6 +154,10 @@ mod tests { assert_eq!(pong.sender(alice).ponged_from(), ping.address()); } + // TODO#q: add has code test + + // TODO#q: add contract::address test + stylus_sdk::stylus_proc::sol_interface! { interface IProxy { #[allow(missing_docs)] @@ -208,6 +212,4 @@ mod tests { assert_eq!(result, value); } - - // TODO#q: add has code test } diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index 97d14d9..fff53d9 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -1,3 +1,8 @@ +//! Router context for external calls mocks. +//! +//! NOTE: [`ROUTER_STORAGE`] should be separated from the main test storage to +//! avoid deadlocks. + use std::{borrow::BorrowMut, sync::Mutex, thread::ThreadId}; use alloy_primitives::{uint, Address}; @@ -72,13 +77,18 @@ impl RouterContext { } } -type RouterStorageKey = (ThreadId, Address); - -/// The key is the name of the test thread, and the value is contract's router -/// data. +/// Router Storage: A global mutable key-value store. +/// Allows concurrent access. +/// +/// The key is the combination of [`ThreadId`] and [`Address`] to avoid +/// a deadlock, while calling more than two contracts consecutive. +/// +/// The value is the router of the contract, generated by `stylus-sdk`. static ROUTER_STORAGE: Lazy> = Lazy::new(DashMap::new); +type RouterStorageKey = (ThreadId, Address); + /// Metadata related to the router of an external contract. struct RouterStorage { // Contract's router. From fbaeafe3be37b9c983b8a82469d866b5386e0c10 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 04:04:54 +0400 Subject: [PATCH 13/42] ++ --- crates/motsu-proc/src/test.rs | 5 ++--- crates/motsu/src/context.rs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/motsu-proc/src/test.rs b/crates/motsu-proc/src/test.rs index 8036c96..757ea75 100644 --- a/crates/motsu-proc/src/test.rs +++ b/crates/motsu-proc/src/test.rs @@ -32,18 +32,17 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { Err(err) => return err.to_compile_error().into(), }; + // Collect contract argument definitions. let contract_arg_defs = arg_binding_and_ty.iter().map(|(arg_binding, contract_ty)| { - // Test case assumes, that contract's variable has `&mut` reference - // to contract's type. quote! { #arg_binding: #contract_ty } }); + // Collect contract argument initializations. let contract_args = arg_binding_and_ty.iter().map(|(_arg_binding, contract_ty)| { - // Pass mutable reference to the contract. quote! { <#contract_ty>::default() } diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 8627fe6..7b3ea0a 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -310,6 +310,8 @@ pub struct Contract { address: Address, } +// TODO#q: add contract drop + impl Default for Contract { fn default() -> Self { Contract::random() From fa44a57ea8fd47cbbc41d60f383dcf9bd2553c9f Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 04:44:52 +0400 Subject: [PATCH 14/42] ++ --- crates/motsu/src/lib.rs | 54 +++++++++++++++++++++++--------------- crates/motsu/src/router.rs | 2 ++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index f28a73e..8e94520 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -48,12 +48,12 @@ //! [test_attribute]: crate::test mod context; pub mod prelude; -mod shims; mod router; +mod shims; pub use motsu_proc::test; -#[cfg(all(test))] +#[cfg(test)] mod tests { #![deny(rustdoc::broken_intra_doc_links)] extern crate alloc; @@ -63,16 +63,16 @@ mod tests { alloy_primitives::{Address, U256}, call::Call, msg, - prelude::{public, storage, TopLevelStorage}, + prelude::{public, storage, AddressVM, TopLevelStorage}, storage::{StorageAddress, StorageU256}, }; use crate::context::{Account, Contract}; #[storage] - pub struct PingContract { - pub _pings_count: StorageU256, - pub _pinged_from: StorageAddress, + struct PingContract { + pings_count: StorageU256, + pinged_from: StorageAddress, } #[public] @@ -83,18 +83,22 @@ mod tests { let value = receiver.pong(call, value).expect("should pong successfully"); - let pings_count = self._pings_count.get(); - self._pings_count.set(pings_count + uint!(1_U256)); - self._pinged_from.set(msg::sender()); + let pings_count = self.pings_count.get(); + self.pings_count.set(pings_count + uint!(1_U256)); + self.pinged_from.set(msg::sender()); Ok(value) } fn ping_count(&self) -> U256 { - self._pings_count.get() + self.pings_count.get() } fn pinged_from(&self) -> Address { - self._pinged_from.get() + self.pinged_from.get() + } + + fn has_pong(&self, to: Address) -> bool { + to.has_code() } } @@ -108,33 +112,33 @@ mod tests { } #[storage] - pub struct PongContract { - pub _pongs_count: StorageU256, - pub _ponged_from: StorageAddress, + struct PongContract { + pongs_count: StorageU256, + ponged_from: StorageAddress, } #[public] impl PongContract { pub fn pong(&mut self, value: U256) -> Result> { - let pongs_count = self._pongs_count.get(); - self._pongs_count.set(pongs_count + uint!(1_U256)); - self._ponged_from.set(msg::sender()); + let pongs_count = self.pongs_count.get(); + self.pongs_count.set(pongs_count + uint!(1_U256)); + self.ponged_from.set(msg::sender()); Ok(value + uint!(1_U256)) } fn pong_count(&self) -> U256 { - self._pongs_count.get() + self.pongs_count.get() } fn ponged_from(&self) -> Address { - self._ponged_from.get() + self.ponged_from.get() } } unsafe impl TopLevelStorage for PongContract {} #[test] - fn ping_pong_works() { + fn ping_pong_msg_sender() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -154,7 +158,15 @@ mod tests { assert_eq!(pong.sender(alice).ponged_from(), ping.address()); } - // TODO#q: add has code test + #[test] + fn ping_pong_has_code() { + let ping = Contract::::default(); + let pong = Contract::::default(); + + let alice = Account::random(); + + assert!(ping.sender(alice).has_pong(pong.address())); + } // TODO#q: add contract::address test diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index fff53d9..1558f21 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -14,6 +14,8 @@ use stylus_sdk::{ ArbResult, }; +// TODO#q: use RouterContext as a key for the storage. + /// Context for the router of a test contract for current test thread and /// contract's address. pub(crate) struct RouterContext { From cb5d91d58c50c0a974c2ccea667e276e5b5eabbe Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 05:24:44 +0400 Subject: [PATCH 15/42] ++ --- crates/motsu/src/context.rs | 32 +++++++------- crates/motsu/src/lib.rs | 85 ++++++++++++++++++++++++------------- crates/motsu/src/shims.rs | 15 ++----- 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 7b3ea0a..a3d4bd4 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -44,7 +44,7 @@ impl Context { fn get_bytes(self, key: &Bytes32) -> Bytes32 { let storage = self.storage(); let msg_receiver = - storage.msg_receiver.expect("msg_receiver should be set"); + storage.contract_address.expect("msg_receiver should be set"); storage .contract_data .get(&msg_receiver) @@ -64,7 +64,7 @@ impl Context { fn set_bytes(self, key: Bytes32, value: Bytes32) { let mut storage = self.storage(); let msg_receiver = - storage.msg_receiver.expect("msg_receiver should be set"); + storage.contract_address.expect("msg_receiver should be set"); storage .contract_data .get_mut(&msg_receiver) @@ -88,14 +88,14 @@ impl Context { self.storage().msg_sender } - /// Set the address of the contract, that should be called. - fn set_msg_receiver(&self, msg_receiver: Address) -> Option
{ - self.storage().msg_receiver.replace(msg_receiver) + /// Set the address of the contract, that is called. + fn set_contract_address(&self, msg_receiver: Address) -> Option
{ + self.storage().contract_address.replace(msg_receiver) } - /// Get the address of the contract, that should be called. - fn get_msg_receiver(&self) -> Option
{ - self.storage().msg_receiver + /// Get the address of the contract, that is called. + pub(crate) fn get_contract_address(&self) -> Option
{ + self.storage().contract_address } /// Initialise contract's storage for the current test thread and @@ -152,13 +152,13 @@ impl Context { selector: u32, input: &[u8], ) -> ArbResult { - // Set the current contract as message sender and callee contract as - // receiver. - let previous_receiver = self - .set_msg_receiver(contract_address) + // Set the caller contract as message sender and callee contract as + // a receiver (`contract_address`). + let previous_contract_address = self + .set_contract_address(contract_address) .expect("msg_receiver should be set"); let previous_msg_sender = self - .set_msg_sender(previous_receiver) + .set_msg_sender(previous_contract_address) .expect("msg_sender should be set"); // Call external contract. @@ -170,7 +170,7 @@ impl Context { }); // Set the previous message sender and receiver back. - let _ = self.set_msg_receiver(previous_receiver); + let _ = self.set_contract_address(previous_contract_address); let _ = self.set_msg_sender(previous_msg_sender); result @@ -254,7 +254,7 @@ struct MockStorage { /// Address of the message sender. msg_sender: Option
, /// Address of the contract that is currently receiving the message. - msg_receiver: Option
, + contract_address: Option
, /// Contract's address to mock data storage mapping. contract_data: HashMap, // Output of a contract call. @@ -282,7 +282,7 @@ impl ContractCall { /// Preset the call parameters. fn set_call_params(&self) { let _ = Context::current().set_msg_sender(self.caller_address); - let _ = Context::current().set_msg_receiver(self.contract_address); + let _ = Context::current().set_contract_address(self.contract_address); } } diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 8e94520..104c321 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -62,7 +62,7 @@ mod tests { use stylus_sdk::{ alloy_primitives::{Address, U256}, call::Call, - msg, + contract, msg, prelude::{public, storage, AddressVM, TopLevelStorage}, storage::{StorageAddress, StorageU256}, }; @@ -73,6 +73,7 @@ mod tests { struct PingContract { pings_count: StorageU256, pinged_from: StorageAddress, + contract_address: StorageAddress, } #[public] @@ -85,16 +86,11 @@ mod tests { let pings_count = self.pings_count.get(); self.pings_count.set(pings_count + uint!(1_U256)); - self.pinged_from.set(msg::sender()); - Ok(value) - } - fn ping_count(&self) -> U256 { - self.pings_count.get() - } + self.pinged_from.set(msg::sender()); + self.contract_address.set(contract::address()); - fn pinged_from(&self) -> Address { - self.pinged_from.get() + Ok(value) } fn has_pong(&self, to: Address) -> bool { @@ -115,6 +111,7 @@ mod tests { struct PongContract { pongs_count: StorageU256, ponged_from: StorageAddress, + contract_address: StorageAddress, } #[public] @@ -122,23 +119,18 @@ mod tests { pub fn pong(&mut self, value: U256) -> Result> { let pongs_count = self.pongs_count.get(); self.pongs_count.set(pongs_count + uint!(1_U256)); - self.ponged_from.set(msg::sender()); - Ok(value + uint!(1_U256)) - } - fn pong_count(&self) -> U256 { - self.pongs_count.get() - } + self.ponged_from.set(msg::sender()); + self.contract_address.set(contract::address()); - fn ponged_from(&self) -> Address { - self.ponged_from.get() + Ok(value + uint!(1_U256)) } } unsafe impl TopLevelStorage for PongContract {} #[test] - fn ping_pong_msg_sender() { + fn ping_pong_works() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -151,11 +143,27 @@ mod tests { .expect("should ping successfully"); assert_eq!(ponged_value, value + uint!(1_U256)); - assert_eq!(ping.sender(alice).ping_count(), uint!(1_U256)); - assert_eq!(pong.sender(alice).pong_count(), uint!(1_U256)); + assert_eq!(ping.sender(alice).pings_count.get(), uint!(1_U256)); + assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); + } + + #[test] + fn ping_pong_msg_sender() { + let ping = Contract::::default(); + let pong = Contract::::default(); - assert_eq!(ping.sender(alice).pinged_from(), alice.address()); - assert_eq!(pong.sender(alice).ponged_from(), ping.address()); + let alice = Account::random(); + + assert_eq!(ping.sender(alice).pinged_from.get(), Address::ZERO); + assert_eq!(pong.sender(alice).ponged_from.get(), Address::ZERO); + + let ponged_value = ping + .sender(alice) + .ping(pong.address(), uint!(10_U256)) + .expect("should ping successfully"); + + assert_eq!(ping.sender(alice).pinged_from.get(), alice.address()); + assert_eq!(pong.sender(alice).ponged_from.get(), ping.address()); } #[test] @@ -168,7 +176,24 @@ mod tests { assert!(ping.sender(alice).has_pong(pong.address())); } - // TODO#q: add contract::address test + #[test] + fn ping_pong_contract_address() { + let ping = Contract::::default(); + let pong = Contract::::default(); + + let alice = Account::random(); + + assert_eq!(ping.sender(alice).contract_address.get(), Address::ZERO); + assert_eq!(pong.sender(alice).contract_address.get(), Address::ZERO); + + let _ = ping + .sender(alice) + .ping(pong.address(), uint!(10_U256)) + .expect("should ping successfully"); + + assert_eq!(ping.sender(alice).contract_address.get(), ping.address()); + assert_eq!(pong.sender(alice).contract_address.get(), pong.address()); + } stylus_sdk::stylus_proc::sol_interface! { interface IProxy { @@ -178,14 +203,14 @@ mod tests { } #[storage] - pub struct Proxy { - pub _next_proxy: StorageAddress, + struct Proxy { + next_proxy: StorageAddress, } #[public] impl Proxy { - pub fn call_proxy(&mut self, value: U256) -> U256 { - let next_proxy = self._next_proxy.get(); + fn call_proxy(&mut self, value: U256) -> U256 { + let next_proxy = self.next_proxy.get(); // If there is no next proxy, return the value. if next_proxy.is_zero() { @@ -210,13 +235,13 @@ mod tests { let alice = Account::random(); proxy1.init(alice, |storage| { - storage._next_proxy.set(proxy2.address()); + storage.next_proxy.set(proxy2.address()); }); proxy2.init(alice, |storage| { - storage._next_proxy.set(proxy3.address()); + storage.next_proxy.set(proxy3.address()); }); proxy3.init(alice, |storage| { - storage._next_proxy.set(Address::ZERO); + storage.next_proxy.set(Address::ZERO); }); let value = uint!(10_U256); diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index c1ad762..feaf3a3 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -116,13 +116,6 @@ pub fn storage_flush_cache(_: bool) { // No-op: we don't use the cache in our unit-tests. } -/// Dummy msg sender set for tests. -pub const MSG_SENDER: &[u8; 42] = b"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"; - -/// Dummy contract address set for tests. -pub const CONTRACT_ADDRESS: &[u8; 42] = - b"0xdCE82b5f92C98F27F116F70491a487EFFDb6a2a9"; - /// Arbitrum's CHAID ID. pub const CHAIN_ID: u64 = 42161; @@ -178,10 +171,10 @@ pub unsafe extern "C" fn msg_value(value: *mut u8) { /// May panic if fails to parse `CONTRACT_ADDRESS` as an address. #[no_mangle] pub unsafe extern "C" fn contract_address(address: *mut u8) { - // TODO#q: mock contract address. Rename msg_receiver -> contract_address. - let addr = - const_hex::const_decode_to_array::<20>(CONTRACT_ADDRESS).unwrap(); - std::ptr::copy(addr.as_ptr(), address, 20); + let contract_address = Context::current() + .get_contract_address() + .expect("contract_address should be set"); + std::ptr::copy(contract_address.as_ptr(), address, 20); } /// Gets the chain ID of the current chain. The semantics are equivalent to From dedb19438fd71af91fac2053b8221da1e4ec7a94 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Dec 2024 05:31:12 +0400 Subject: [PATCH 16/42] ++ --- crates/motsu/src/lib.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 104c321..64b8adf 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -54,10 +54,11 @@ mod shims; pub use motsu_proc::test; #[cfg(test)] -mod tests { - #![deny(rustdoc::broken_intra_doc_links)] - extern crate alloc; +extern crate alloc; +#[cfg(test)] +mod ping_pong_tests { + #![deny(rustdoc::broken_intra_doc_links)] use alloy_primitives::uint; use stylus_sdk::{ alloy_primitives::{Address, U256}, @@ -130,7 +131,7 @@ mod tests { unsafe impl TopLevelStorage for PongContract {} #[test] - fn ping_pong_works() { + fn external_call() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -146,9 +147,9 @@ mod tests { assert_eq!(ping.sender(alice).pings_count.get(), uint!(1_U256)); assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); } - + #[test] - fn ping_pong_msg_sender() { + fn msg_sender() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -167,7 +168,7 @@ mod tests { } #[test] - fn ping_pong_has_code() { + fn has_code() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -177,7 +178,7 @@ mod tests { } #[test] - fn ping_pong_contract_address() { + fn contract_address() { let ping = Contract::::default(); let pong = Contract::::default(); @@ -194,6 +195,18 @@ mod tests { assert_eq!(ping.sender(alice).contract_address.get(), ping.address()); assert_eq!(pong.sender(alice).contract_address.get(), pong.address()); } +} + +#[cfg(test)] +mod proxies_tests { + use alloy_primitives::{uint, Address, U256}; + use stylus_sdk::{ + call::Call, + prelude::{public, storage, TopLevelStorage}, + storage::StorageAddress, + }; + + use crate::context::{Account, Contract}; stylus_sdk::stylus_proc::sol_interface! { interface IProxy { From 1888d3cb57ce4ecb76cf0f3159ebb900709d0abe Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 31 Dec 2024 15:19:15 +0400 Subject: [PATCH 17/42] use context as a key for storage --- crates/motsu/src/context.rs | 63 +++++++++++++++++-------------------- crates/motsu/src/router.rs | 58 +++++++++++++++------------------- 2 files changed, 54 insertions(+), 67 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index a3d4bd4..4d8dde5 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -12,25 +12,25 @@ use crate::{ router::{RouterContext, TestRouter}, }; +/// Storage mock: A global mutable key-value store. +/// Allows concurrent access. +/// +/// The key is the test [`Context`], an id of the test thread, and the value is +/// the [`MockStorage`], a storage of the test case. +static STORAGE: Lazy> = Lazy::new(DashMap::new); + /// Context of stylus unit tests associated with the current test thread. #[allow(clippy::module_name_repetitions)] +#[derive(Hash, Eq, PartialEq, Copy, Clone)] pub struct Context { - thread: std::thread::Thread, + thread_id: ThreadId, } impl Context { /// Get test context associated with the current test thread. #[must_use] pub fn current() -> Self { - Self { thread: std::thread::current() } - } - - /// Get the name of the current test thread. - pub fn thread_name(&self) -> String { - self.thread - .name() - .expect("should retrieve current thread name") - .to_string() + Self { thread_id: std::thread::current().id() } } /// Get the raw value at `key` in storage and write it to `value`. @@ -75,26 +75,27 @@ impl Context { /// Clears storage, removing all key-value pairs associated with the current /// test thread. pub fn reset_storage(self) { - STORAGE.remove(&self.thread.id()); + STORAGE.remove(&self); } /// Set the message sender account address. - fn set_msg_sender(&self, msg_sender: Address) -> Option
{ + fn set_msg_sender(self, msg_sender: Address) -> Option
{ self.storage().msg_sender.replace(msg_sender) } /// Get the message sender account address. - pub fn get_msg_sender(&self) -> Option
{ + #[must_use] + pub fn get_msg_sender(self) -> Option
{ self.storage().msg_sender } /// Set the address of the contract, that is called. - fn set_contract_address(&self, msg_receiver: Address) -> Option
{ + fn set_contract_address(self, msg_receiver: Address) -> Option
{ self.storage().contract_address.replace(msg_receiver) } /// Get the address of the contract, that is called. - pub(crate) fn get_contract_address(&self) -> Option
{ + pub(crate) fn get_contract_address(self) -> Option
{ self.storage().contract_address } @@ -105,7 +106,7 @@ impl Context { contract_address: Address, ) { if STORAGE - .entry(self.thread.id()) + .entry(self) .or_default() .contract_data .insert(contract_address, HashMap::new()) @@ -147,7 +148,7 @@ impl Context { } fn call_contract( - &self, + self, contract_address: Address, selector: u32, input: &[u8], @@ -176,7 +177,7 @@ impl Context { result } - fn set_return_data(&self, data: Vec) { + fn set_return_data(self, data: Vec) { let mut call_storage = self.storage(); let _ = call_storage.call_output_len.insert(data.len()); let _ = call_storage.call_output.insert(data); @@ -192,14 +193,14 @@ impl Context { data.len() } - pub(crate) fn get_return_data_size(&self) -> usize { + pub(crate) fn get_return_data_size(self) -> usize { self.storage() .call_output_len .take() .expect("call_output_len should be set") } - fn get_return_data(&self) -> Vec { + fn get_return_data(self) -> Vec { self.storage().call_output.take().expect("call_output should be set") } @@ -212,20 +213,18 @@ impl Context { /// Check if the contract at `address` has code. #[must_use] - fn has_code(&self, address: Address) -> bool { + fn has_code(self, address: Address) -> bool { self.router_for(address).exists() } /// Get reference to the storage for the current test thread. - fn storage(&self) -> RefMut<'static, ThreadId, MockStorage> { - STORAGE - .get_mut(&self.thread.id()) - .expect("contract should be initialised first") + fn storage(self) -> RefMut<'static, Context, MockStorage> { + STORAGE.get_mut(&self).expect("contract should be initialised first") } /// Get router for the contract at `address`. - fn router_for(&self, address: Address) -> RouterContext { - RouterContext::new(self.thread.clone(), address) + fn router_for(self, address: Address) -> RouterContext { + RouterContext::new(self.thread_id, address) } } @@ -241,13 +240,6 @@ unsafe fn write_bytes32(ptr: *mut u8, bytes: Bytes32) { ptr::copy(bytes.as_ptr(), ptr, WORD_BYTES); } -/// Storage mock: A global mutable key-value store. -/// Allows concurrent access. -/// -/// The key is an id of the test thread, and the value is the storage of the -/// test case. -static STORAGE: Lazy> = Lazy::new(DashMap::new); - /// Storage for unit test's mock data. #[derive(Default)] struct MockStorage { @@ -334,16 +326,19 @@ impl Contract { } /// Create a new contract with random address. + #[must_use] pub fn random() -> Self { Self::new(Address::random()) } /// Get contract's test address. + #[must_use] pub fn address(&self) -> Address { self.address } /// Call contract `self` with `account` as a sender. + #[must_use] pub fn sender(&self, account: Account) -> ContractCall { ContractCall { contract: unsafe { ST::new(uint!(0_U256), 0) }, diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index 1558f21..f4cbd23 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -14,42 +14,46 @@ use stylus_sdk::{ ArbResult, }; -// TODO#q: use RouterContext as a key for the storage. +/// Router Storage: A global mutable key-value store. +/// Allows concurrent access. +/// +/// The key is the [`RouterContext`], a combination of [`ThreadId`] and +/// [`Address`] to avoid a deadlock, while calling more than two contracts +/// consecutive. +/// +/// The value is the [`RouterStorage`], a router of the contract generated by +/// `stylus-sdk`. +static ROUTER_STORAGE: Lazy> = + Lazy::new(DashMap::new); /// Context for the router of a test contract for current test thread and /// contract's address. +#[derive(Hash, Eq, PartialEq, Copy, Clone)] pub(crate) struct RouterContext { - thread: std::thread::Thread, + thread_id: ThreadId, contract_address: Address, } impl RouterContext { /// Create a new router context. - pub(crate) fn new( - thread: std::thread::Thread, - contract_address: Address, - ) -> Self { - Self { thread, contract_address } + pub(crate) fn new(thread: ThreadId, contract_address: Address) -> Self { + Self { thread_id: thread, contract_address } } /// Get reference to the call storage for the current test thread. - fn storage(&self) -> RefMut<'static, RouterStorageKey, RouterStorage> { + fn storage(self) -> RefMut<'static, RouterContext, RouterStorage> { ROUTER_STORAGE - .get_mut(&self.storage_key()) + .get_mut(&self) .expect("contract should be initialised first") } /// Check if the router exists for the contract. - pub(crate) fn exists(&self) -> bool { - ROUTER_STORAGE.contains_key(&self.storage_key()) - } - - fn storage_key(&self) -> RouterStorageKey { - (self.thread.id(), self.contract_address) + pub(crate) fn exists(self) -> bool { + ROUTER_STORAGE.contains_key(&self) } pub(crate) fn route( - &self, + self, selector: u32, input: &[u8], ) -> Option { @@ -61,11 +65,10 @@ impl RouterContext { /// Initialise contract router for the current test thread and /// `contract_address`. pub(crate) fn init_storage(self) { - let storage_key = self.storage_key(); let contract_address = self.contract_address; if ROUTER_STORAGE .insert( - storage_key, + self, RouterStorage { router: Mutex::new(Box::new(unsafe { ST::new(uint!(0_U256), 0) @@ -79,18 +82,6 @@ impl RouterContext { } } -/// Router Storage: A global mutable key-value store. -/// Allows concurrent access. -/// -/// The key is the combination of [`ThreadId`] and [`Address`] to avoid -/// a deadlock, while calling more than two contracts consecutive. -/// -/// The value is the router of the contract, generated by `stylus-sdk`. -static ROUTER_STORAGE: Lazy> = - Lazy::new(DashMap::new); - -type RouterStorageKey = (ThreadId, Address); - /// Metadata related to the router of an external contract. struct RouterStorage { // Contract's router. @@ -99,9 +90,10 @@ struct RouterStorage { } /// A trait for routing messages to the appropriate selector in tests. -pub(crate) trait TestRouter: Send { - /// Tries to find and execute a method for the given selector, returning - /// `None` if none is found. +#[allow(clippy::module_name_repetitions)] +pub trait TestRouter: Send { + /// Tries to find and execute a method for the given `selector`, returning + /// `None` if the `selector` wasn't found. fn route(&mut self, selector: u32, input: &[u8]) -> Option; } From a4ea0eab9717623e6e0a0fb4566798b2655a594d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 31 Dec 2024 17:49:08 +0400 Subject: [PATCH 18/42] impl Drop for Contract --- crates/motsu-proc/src/test.rs | 4 +-- crates/motsu/src/context.rs | 57 +++++++++++++++++++++++++---------- crates/motsu/src/router.rs | 5 +++ crates/motsu/src/shims.rs | 33 +++++++++----------- 4 files changed, 62 insertions(+), 37 deletions(-) diff --git a/crates/motsu-proc/src/test.rs b/crates/motsu-proc/src/test.rs index 757ea75..bdd1e47 100644 --- a/crates/motsu-proc/src/test.rs +++ b/crates/motsu-proc/src/test.rs @@ -56,9 +56,7 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { #[test] fn #fn_name() #fn_return_type { let test = | #( #contract_arg_defs ),* | #fn_block; - let res = test( #( #contract_args ),* ); - ::motsu::prelude::Context::current().reset_storage(); - res + test( #( #contract_args ),* ) } } .into() diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 4d8dde5..5d54b22 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -15,8 +15,9 @@ use crate::{ /// Storage mock: A global mutable key-value store. /// Allows concurrent access. /// -/// The key is the test [`Context`], an id of the test thread, and the value is -/// the [`MockStorage`], a storage of the test case. +/// The key is the test [`Context`], an id of the test thread. +/// +/// The value is the [`MockStorage`], a storage of the test case. static STORAGE: Lazy> = Lazy::new(DashMap::new); /// Context of stylus unit tests associated with the current test thread. @@ -72,19 +73,13 @@ impl Context { .insert(key, value); } - /// Clears storage, removing all key-value pairs associated with the current - /// test thread. - pub fn reset_storage(self) { - STORAGE.remove(&self); - } - /// Set the message sender account address. fn set_msg_sender(self, msg_sender: Address) -> Option
{ self.storage().msg_sender.replace(msg_sender) } /// Get the message sender account address. - #[must_use] + #[must_use] pub fn get_msg_sender(self) -> Option
{ self.storage().msg_sender } @@ -112,10 +107,29 @@ impl Context { .insert(contract_address, HashMap::new()) .is_some() { - panic!("contract storage already initialized - contract_address is {contract_address}"); + panic!("contract storage already initialized for contract_address `{contract_address}`"); } - self.router_for(contract_address).init_storage::(); + self.router(contract_address).init_storage::(); + } + + /// Reset storage for the current [`Context`] and `contract_address`. + /// + /// If all test contracts are removed, flush storage for the current + /// test [`Context`]. + fn reset_storage(self, contract_address: Address) { + let mut storage = self.storage(); + storage.contract_data.remove(&contract_address); + + // if no more contracts left, + if storage.contract_data.is_empty() { + // drop guard to a concurrent hash map to avoid a deadlock, + drop(storage); + // and erase the test context. + let _ = STORAGE.remove(&self); + } + + self.router(contract_address).reset_storage(); } /// Call the contract at raw `address` with the given raw `calldata`. @@ -147,6 +161,8 @@ impl Context { } } + /// Call the function associated with the given `selector` and pass `input` + /// to it, at the given `contract_address`. fn call_contract( self, contract_address: Address, @@ -164,25 +180,28 @@ impl Context { // Call external contract. let result = self - .router_for(contract_address) + .router(contract_address) .route(selector, input) .unwrap_or_else(|| { panic!("selector not found - selector is {selector}") }); - // Set the previous message sender and receiver back. + // Set the previous message sender and contract address back. let _ = self.set_contract_address(previous_contract_address); let _ = self.set_msg_sender(previous_msg_sender); result } + /// Set return data as bytes. fn set_return_data(self, data: Vec) { let mut call_storage = self.storage(); let _ = call_storage.call_output_len.insert(data.len()); let _ = call_storage.call_output.insert(data); } + /// Read the return data (with a given `size`) from the last contract call + /// to the `dest` pointer. pub(crate) unsafe fn read_return_data_raw( self, dest: *mut u8, @@ -193,6 +212,7 @@ impl Context { data.len() } + /// Get return data's size in bytes. pub(crate) fn get_return_data_size(self) -> usize { self.storage() .call_output_len @@ -200,6 +220,7 @@ impl Context { .expect("call_output_len should be set") } + /// Get return data's bytes. fn get_return_data(self) -> Vec { self.storage().call_output.take().expect("call_output should be set") } @@ -214,7 +235,7 @@ impl Context { /// Check if the contract at `address` has code. #[must_use] fn has_code(self, address: Address) -> bool { - self.router_for(address).exists() + self.router(address).exists() } /// Get reference to the storage for the current test thread. @@ -223,7 +244,7 @@ impl Context { } /// Get router for the contract at `address`. - fn router_for(self, address: Address) -> RouterContext { + fn router(self, address: Address) -> RouterContext { RouterContext::new(self.thread_id, address) } } @@ -302,7 +323,11 @@ pub struct Contract { address: Address, } -// TODO#q: add contract drop +impl Drop for Contract { + fn drop(&mut self) { + Context::current().reset_storage(self.address); + } +} impl Default for Contract { fn default() -> Self { diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index f4cbd23..255fb10 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -80,6 +80,11 @@ impl RouterContext { panic!("contract's router is already initialized - contract_address is {contract_address}"); } } + + /// Reset router storage for the current [`RouterContext`]. + pub(crate) fn reset_storage(self) { + ROUTER_STORAGE.remove(&self); + } } /// Metadata related to the router of an external contract. diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index feaf3a3..aa52b1d 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -123,18 +123,15 @@ pub const CHAIN_ID: u64 = 42161; pub const EOA_CODEHASH: &[u8; 66] = b"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; -/// Gets the address of the account that called the program. -/// -/// For normal L2-to-L2 transactions the semantics are equivalent to that of the -/// EVM's [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. /// Contract Account (CA) code hash (smart contract code). /// NOTE: can be any 256-bit value to pass `has_code` check. pub const CA_CODEHASH: &[u8; 66] = b"0x1111111111111111111111111111111111111111111111111111111111111111"; -/// Gets the address of the account that called the program. For normal -/// L2-to-L2 transactions the semantics are equivalent to that of the EVM's -/// [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. +/// Gets the address of the account that called the program. +/// +/// For normal L2-to-L2 transactions the semantics are equivalent to that of the +/// EVM's [`CALLER`] opcode, including in cases arising from [`DELEGATE_CALL`]. /// /// For L1-to-L2 retryable ticket transactions, the top-level sender's address /// will be aliased. See [`Retryable Ticket Address Aliasing`][aliasing] for @@ -252,11 +249,11 @@ pub unsafe extern "C" fn return_data_size() -> usize { /// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e #[no_mangle] pub unsafe extern "C" fn read_return_data( - _dest: *mut u8, + dest: *mut u8, _offset: usize, - _size: usize, + size: usize, ) -> usize { - Context::current().read_return_data_raw(_dest, _size) + Context::current().read_return_data_raw(dest, size) } /// Calls the contract at the given address with options for passing value and @@ -275,18 +272,18 @@ pub unsafe extern "C" fn read_return_data( /// [`CALL`]: https://www.evm.codes/#f1 #[no_mangle] pub unsafe extern "C" fn call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, + contract: *const u8, + calldata: *const u8, + calldata_len: usize, _value: *const u8, _gas: u64, - _return_data_len: *mut usize, + return_data_len: *mut usize, ) -> u8 { Context::current().call_contract_raw( - _contract, - _calldata, - _calldata_len, - _return_data_len, + contract, + calldata, + calldata_len, + return_data_len, ) } From 7bf0dd3fd281755d90f7379fdf952497e49d5212 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 31 Dec 2024 18:33:00 +0400 Subject: [PATCH 19/42] sender accept generic account --- crates/motsu/src/context.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 5d54b22..bc49c0a 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -364,10 +364,10 @@ impl Contract { /// Call contract `self` with `account` as a sender. #[must_use] - pub fn sender(&self, account: Account) -> ContractCall { + pub fn sender>(&self, account: A) -> ContractCall { ContractCall { contract: unsafe { ST::new(uint!(0_U256), 0) }, - caller_address: account.address, + caller_address: account.into(), contract_address: self.address, } } @@ -379,6 +379,12 @@ pub struct Account { address: Address, } +impl From for Address { + fn from(value: Account) -> Self { + value.address + } +} + impl Account { /// Create a new account with the given `address`. #[must_use] From 366a6f5f188981b27765b0d1761733eaa742a3ee Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 31 Dec 2024 20:54:33 +0400 Subject: [PATCH 20/42] init accept generic account --- crates/motsu/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index bc49c0a..db4611f 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -346,8 +346,8 @@ impl Contract { /// Initialize the contract with an `initializer` function, and on behalf of /// the given `account`. - pub fn init(&self, account: Account, initializer: impl FnOnce(&mut ST)) { - initializer(&mut self.sender(account)); + pub fn init>(&self, account: A, initializer: impl FnOnce(&mut ST)) { + initializer(&mut self.sender(account.into())); } /// Create a new contract with random address. From 7be9b8644e42bfacee815882728cb268c90f901a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 31 Dec 2024 22:03:14 +0400 Subject: [PATCH 21/42] ++ --- crates/motsu/src/context.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index db4611f..b262e4b 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -44,11 +44,11 @@ impl Context { /// Get the value at `key` in storage. fn get_bytes(self, key: &Bytes32) -> Bytes32 { let storage = self.storage(); - let msg_receiver = - storage.contract_address.expect("msg_receiver should be set"); + let contract_address = + storage.contract_address.expect("contract_address should be set"); storage .contract_data - .get(&msg_receiver) + .get(&contract_address) .expect("contract receiver should have a storage initialised") .get(key) .copied() @@ -64,11 +64,11 @@ impl Context { /// Set the value at `key` in storage to `value`. fn set_bytes(self, key: Bytes32, value: Bytes32) { let mut storage = self.storage(); - let msg_receiver = - storage.contract_address.expect("msg_receiver should be set"); + let contract_address = + storage.contract_address.expect("contract_address should be set"); storage .contract_data - .get_mut(&msg_receiver) + .get_mut(&contract_address) .expect("contract receiver should have a storage initialised") .insert(key, value); } @@ -85,8 +85,8 @@ impl Context { } /// Set the address of the contract, that is called. - fn set_contract_address(self, msg_receiver: Address) -> Option
{ - self.storage().contract_address.replace(msg_receiver) + fn set_contract_address(self, address: Address) -> Option
{ + self.storage().contract_address.replace(address) } /// Get the address of the contract, that is called. @@ -173,7 +173,7 @@ impl Context { // a receiver (`contract_address`). let previous_contract_address = self .set_contract_address(contract_address) - .expect("msg_receiver should be set"); + .expect("contract_address should be set"); let previous_msg_sender = self .set_msg_sender(previous_contract_address) .expect("msg_sender should be set"); From 1ffba323a5f10efa4947901158d18100fb007b12 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 7 Jan 2025 15:57:51 +0400 Subject: [PATCH 22/42] update stylus sdk --- Cargo.lock | 10 ++++++---- Cargo.toml | 13 ++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3c1871..458e308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,8 +1441,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stylus-proc" -version = "0.7.0-rc.1" -source = "git+https://github.com/OffchainLabs/stylus-sdk-rs?branch=rel/0.7.0-rc.1#754c69be04c0017a1d2573f2aa5b0c5f2483288a" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0ee8d057be480a457704f982a38254e13a2a7d42bb4531132d3d440e371a6" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -1461,8 +1462,9 @@ dependencies = [ [[package]] name = "stylus-sdk" -version = "0.7.0-rc.1" -source = "git+https://github.com/OffchainLabs/stylus-sdk-rs?branch=rel/0.7.0-rc.1#754c69be04c0017a1d2573f2aa5b0c5f2483288a" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec9700e00e9dfa862aad80e6b27e02e9fb0c6745f3f97f616368936faefad5f" dependencies = [ "alloy-primitives", "alloy-sol-types", diff --git a/Cargo.toml b/Cargo.toml index 9bfcf86..ef554d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ all = "warn" const-hex = { version = "1.11.1", default-features = false } once_cell = "1.19.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } -stylus-sdk = { version = "0.7.0-rc.1", default-features = false } +stylus-sdk = { version = "0.7.0", default-features = false } dashmap = "6.1.0" syn = { version = "2.0.58", features = ["full"] } proc-macro2 = "1.0.79" @@ -53,13 +53,4 @@ panic = "abort" default = { extend-ignore-identifiers-re = [ # ignore hex data samples. "[0-9a-fA-F][0-9a-fA-F]", -] } - -## TODO#q: remove this once the fix is released -[patch.crates-io.stylus-sdk] -git = "https://github.com/OffchainLabs/stylus-sdk-rs" -branch = "rel/0.7.0-rc.1" - -[patch.crates-io.stylus-proc] -git = "https://github.com/OffchainLabs/stylus-sdk-rs" -branch = "rel/0.7.0-rc.1" \ No newline at end of file +] } \ No newline at end of file From e6b51962da23872bfd1946aa6b4acf7e92d55998 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 15 Jan 2025 15:45:17 +0400 Subject: [PATCH 23/42] fmt --- crates/motsu/src/context.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index b262e4b..9fd16bb 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -346,7 +346,11 @@ impl Contract { /// Initialize the contract with an `initializer` function, and on behalf of /// the given `account`. - pub fn init>(&self, account: A, initializer: impl FnOnce(&mut ST)) { + pub fn init>( + &self, + account: A, + initializer: impl FnOnce(&mut ST), + ) { initializer(&mut self.sender(account.into())); } From f1cd300fb0a27aaa598bb2384cec36fecbefe571 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 15 Jan 2025 19:57:00 +0400 Subject: [PATCH 24/42] ++ --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 458e308..4e971ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -831,7 +831,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "motsu" -version = "0.2.1" +version = "0.3.0" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "motsu-proc" -version = "0.2.1" +version = "0.3.0" dependencies = [ "alloy-primitives", "alloy-sol-types", From 8ae0ebd08e03733f4f703326d25894fea24eddda Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 15 Jan 2025 22:06:27 +0400 Subject: [PATCH 25/42] make storage_flush_cache available for linking --- crates/motsu/src/shims.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index aa52b1d..fd6149e 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -112,7 +112,8 @@ pub unsafe extern "C" fn storage_cache_bytes32( /// of [`SSTORE`]. /// /// [`SSTORE`]: https://www.evm.codes/#55 -pub fn storage_flush_cache(_: bool) { +#[no_mangle] +pub unsafe extern "C" fn storage_flush_cache(_: bool) { // No-op: we don't use the cache in our unit-tests. } From 50c39719fbc96933c5306e6ef36f2086446ca42e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 16 Jan 2025 19:07:13 +0400 Subject: [PATCH 26/42] init function is capable of returning value --- crates/motsu/src/context.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 9fd16bb..c1b20d0 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -346,12 +346,12 @@ impl Contract { /// Initialize the contract with an `initializer` function, and on behalf of /// the given `account`. - pub fn init>( + pub fn init, Output>( &self, account: A, - initializer: impl FnOnce(&mut ST), - ) { - initializer(&mut self.sender(account.into())); + initializer: impl FnOnce(&mut ST) -> Output, + ) -> Output { + initializer(&mut self.sender(account.into())) } /// Create a new contract with random address. From 41e9882712cccc82d36fa037c302eda1359b2922 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 17 Jan 2025 00:52:26 +0400 Subject: [PATCH 27/42] call random function for test arguments --- crates/motsu-proc/src/test.rs | 39 ++++++++++++++++------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/crates/motsu-proc/src/test.rs b/crates/motsu-proc/src/test.rs index bdd1e47..097eeb4 100644 --- a/crates/motsu-proc/src/test.rs +++ b/crates/motsu-proc/src/test.rs @@ -22,9 +22,9 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { let FnArg::Typed(arg) = arg else { error!(@arg, "unexpected receiver argument in test signature"); }; - let contract_arg_binding = &arg.pat; - let contract_ty = &arg.ty; - Ok((contract_arg_binding, contract_ty)) + let arg_binding = &arg.pat; + let arg_ty = &arg.ty; + Ok((arg_binding, arg_ty)) }) .collect::, _>>() { @@ -32,31 +32,28 @@ pub(crate) fn test(_attr: &TokenStream, input: TokenStream) -> TokenStream { Err(err) => return err.to_compile_error().into(), }; - // Collect contract argument definitions. - let contract_arg_defs = - arg_binding_and_ty.iter().map(|(arg_binding, contract_ty)| { - quote! { - #arg_binding: #contract_ty - } - }); + // Collect argument definitions. + let arg_defs = arg_binding_and_ty.iter().map(|(arg_binding, arg_ty)| { + quote! { + #arg_binding: #arg_ty + } + }); - // Collect contract argument initializations. - let contract_args = - arg_binding_and_ty.iter().map(|(_arg_binding, contract_ty)| { - quote! { - <#contract_ty>::default() - } - }); + // Collect argument initializations. + let arg_inits = arg_binding_and_ty.iter().map(|(_arg_binding, arg_ty)| { + quote! { + <#arg_ty>::random() + } + }); // Declare test case closure. - // Pass mut ref to the test closure and call it. - // Reset storage for the test context and return test's output. + // Pass arguments to the test closure and call it. quote! { #( #attrs )* #[test] fn #fn_name() #fn_return_type { - let test = | #( #contract_arg_defs ),* | #fn_block; - test( #( #contract_args ),* ) + let test = | #( #arg_defs ),* | #fn_block; + test( #( #arg_inits ),* ) } } .into() From a9c402f499ebb464f16177d5501ed35160e3338a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 17 Jan 2025 01:27:17 +0400 Subject: [PATCH 28/42] update example with account injection --- crates/motsu/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 64b8adf..514e523 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -24,8 +24,10 @@ //! use stylus_sdk::alloy_primitives::{Address, U256}; //! //! #[motsu::test] -//! fn reads_balance(contract: Contract) { -//! let alice = Account::random(); +//! fn reads_balance( +//! contract: Contract, +//! alice: Account, +//! ) { //! let balance = contract.sender(alice).balance_of(Address::ZERO); // Access storage. //! assert_eq!(balance, U256::ZERO); //! } From 01985a07dd8994a9e5e154aa1431761c66f08a09 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 21 Jan 2025 17:49:19 +0400 Subject: [PATCH 29/42] rename new(address) -> new_at(address) and add new() method --- crates/motsu/src/context.rs | 18 ++++++++++++------ crates/motsu/src/lib.rs | 22 +++++++++++----------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index c1b20d0..b06409c 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -331,14 +331,20 @@ impl Drop for Contract { impl Default for Contract { fn default() -> Self { - Contract::random() + Contract::new_at(Address::default()) } } impl Contract { + /// Create a new contract with default storage on the random address. + #[must_use] + pub fn new() -> Self { + Self::random() + } + /// Create a new contract with the given `address`. #[must_use] - pub fn new(address: Address) -> Self { + pub fn new_at(address: Address) -> Self { Context::current().init_storage::(address); Self { phantom: ::core::marker::PhantomData, address } @@ -354,10 +360,10 @@ impl Contract { initializer(&mut self.sender(account.into())) } - /// Create a new contract with random address. + /// Create a new contract with default storage on the random address. #[must_use] pub fn random() -> Self { - Self::new(Address::random()) + Self::new_at(Address::random()) } /// Get contract's test address. @@ -392,14 +398,14 @@ impl From for Address { impl Account { /// Create a new account with the given `address`. #[must_use] - pub const fn new(address: Address) -> Self { + pub const fn new_at(address: Address) -> Self { Self { address } } /// Create a new account with random address. #[must_use] pub fn random() -> Self { - Self::new(Address::random()) + Self::new_at(Address::random()) } /// Get account's address. diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 514e523..802260b 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -134,8 +134,8 @@ mod ping_pong_tests { #[test] fn external_call() { - let ping = Contract::::default(); - let pong = Contract::::default(); + let ping = Contract::::new(); + let pong = Contract::::new(); let alice = Account::random(); @@ -152,8 +152,8 @@ mod ping_pong_tests { #[test] fn msg_sender() { - let ping = Contract::::default(); - let pong = Contract::::default(); + let ping = Contract::::new(); + let pong = Contract::::new(); let alice = Account::random(); @@ -171,8 +171,8 @@ mod ping_pong_tests { #[test] fn has_code() { - let ping = Contract::::default(); - let pong = Contract::::default(); + let ping = Contract::::new(); + let pong = Contract::::new(); let alice = Account::random(); @@ -181,8 +181,8 @@ mod ping_pong_tests { #[test] fn contract_address() { - let ping = Contract::::default(); - let pong = Contract::::default(); + let ping = Contract::::new(); + let pong = Contract::::new(); let alice = Account::random(); @@ -243,9 +243,9 @@ mod proxies_tests { #[test] fn three_proxies() { - let proxy1 = Contract::::default(); - let proxy2 = Contract::::default(); - let proxy3 = Contract::::default(); + let proxy1 = Contract::::new(); + let proxy2 = Contract::::new(); + let proxy3 = Contract::::new(); let alice = Account::random(); From 6c52351cca6fbea619d2ca73144c9bcec5440b19 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 22 Jan 2025 14:32:43 +0400 Subject: [PATCH 30/42] update three_proxies test --- crates/motsu/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 802260b..9edaa06 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -226,6 +226,9 @@ mod proxies_tests { impl Proxy { fn call_proxy(&mut self, value: U256) -> U256 { let next_proxy = self.next_proxy.get(); + + // Add one to the value. + let value = value + uint!(1_U256); // If there is no next proxy, return the value. if next_proxy.is_zero() { @@ -262,6 +265,7 @@ mod proxies_tests { let value = uint!(10_U256); let result = proxy1.sender(alice).call_proxy(value); - assert_eq!(result, value); + // The value is incremented by 1 for each proxy. + assert_eq!(result, value + uint!(3_U256)); } } From 8ec42e6d3f5bf5fc010708cc37afdb50be571d61 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 22 Jan 2025 14:48:50 +0400 Subject: [PATCH 31/42] inject contracts through arguments --- crates/motsu/src/lib.rs | 74 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 9edaa06..9d379da 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -132,13 +132,12 @@ mod ping_pong_tests { unsafe impl TopLevelStorage for PongContract {} - #[test] - fn external_call() { - let ping = Contract::::new(); - let pong = Contract::::new(); - - let alice = Account::random(); - + #[motsu_proc::test] + fn external_call( + ping: Contract, + pong: Contract, + alice: Account, + ) { let value = uint!(10_U256); let ponged_value = ping .sender(alice) @@ -150,17 +149,16 @@ mod ping_pong_tests { assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); } - #[test] - fn msg_sender() { - let ping = Contract::::new(); - let pong = Contract::::new(); - - let alice = Account::random(); - + #[motsu_proc::test] + fn msg_sender( + ping: Contract, + pong: Contract, + alice: Account, + ) { assert_eq!(ping.sender(alice).pinged_from.get(), Address::ZERO); assert_eq!(pong.sender(alice).ponged_from.get(), Address::ZERO); - let ponged_value = ping + let _ = ping .sender(alice) .ping(pong.address(), uint!(10_U256)) .expect("should ping successfully"); @@ -169,23 +167,21 @@ mod ping_pong_tests { assert_eq!(pong.sender(alice).ponged_from.get(), ping.address()); } - #[test] - fn has_code() { - let ping = Contract::::new(); - let pong = Contract::::new(); - - let alice = Account::random(); - + #[motsu_proc::test] + fn has_code( + ping: Contract, + pong: Contract, + alice: Account, + ) { assert!(ping.sender(alice).has_pong(pong.address())); } - #[test] - fn contract_address() { - let ping = Contract::::new(); - let pong = Contract::::new(); - - let alice = Account::random(); - + #[motsu_proc::test] + fn contract_address( + ping: Contract, + pong: Contract, + alice: Account, + ) { assert_eq!(ping.sender(alice).contract_address.get(), Address::ZERO); assert_eq!(pong.sender(alice).contract_address.get(), Address::ZERO); @@ -226,7 +222,7 @@ mod proxies_tests { impl Proxy { fn call_proxy(&mut self, value: U256) -> U256 { let next_proxy = self.next_proxy.get(); - + // Add one to the value. let value = value + uint!(1_U256); @@ -244,14 +240,15 @@ mod proxies_tests { unsafe impl TopLevelStorage for Proxy {} - #[test] - fn three_proxies() { - let proxy1 = Contract::::new(); - let proxy2 = Contract::::new(); - let proxy3 = Contract::::new(); - - let alice = Account::random(); - + #[motsu_proc::test] + fn three_proxies( + proxy1: Contract, + proxy2: Contract, + proxy3: Contract, + alice: Account, + ) { + // Set up a chain of three proxies. + // With the given call chain: proxy1 -> proxy2 -> proxy3. proxy1.init(alice, |storage| { storage.next_proxy.set(proxy2.address()); }); @@ -262,6 +259,7 @@ mod proxies_tests { storage.next_proxy.set(Address::ZERO); }); + // Call the first proxy. let value = uint!(10_U256); let result = proxy1.sender(alice).call_proxy(value); From 66c962e33955644c536e134cd7add301f4829419 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 23 Jan 2025 09:28:06 +0400 Subject: [PATCH 32/42] remove get prefix --- crates/motsu/src/context.rs | 14 +++++++------- crates/motsu/src/lib.rs | 2 +- crates/motsu/src/shims.rs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index b06409c..f8555b8 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -80,7 +80,7 @@ impl Context { /// Get the message sender account address. #[must_use] - pub fn get_msg_sender(self) -> Option
{ + pub fn msg_sender(self) -> Option
{ self.storage().msg_sender } @@ -90,7 +90,7 @@ impl Context { } /// Get the address of the contract, that is called. - pub(crate) fn get_contract_address(self) -> Option
{ + pub(crate) fn contract_address(self) -> Option
{ self.storage().contract_address } @@ -207,21 +207,21 @@ impl Context { dest: *mut u8, size: usize, ) -> usize { - let data = self.get_return_data(); + let data = self.return_data(); ptr::copy(data.as_ptr(), dest, size); data.len() } - /// Get return data's size in bytes. - pub(crate) fn get_return_data_size(self) -> usize { + /// Return data's size in bytes from the last contract call. + pub(crate) fn return_data_size(self) -> usize { self.storage() .call_output_len .take() .expect("call_output_len should be set") } - /// Get return data's bytes. - fn get_return_data(self) -> Vec { + /// Return data's bytes from the last contract call. + fn return_data(self) -> Vec { self.storage().call_output.take().expect("call_output should be set") } diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 9d379da..d87efb5 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -119,7 +119,7 @@ mod ping_pong_tests { #[public] impl PongContract { - pub fn pong(&mut self, value: U256) -> Result> { + fn pong(&mut self, value: U256) -> Result> { let pongs_count = self.pongs_count.get(); self.pongs_count.set(pongs_count + uint!(1_U256)); diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index fd6149e..eac979f 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -148,7 +148,7 @@ pub const CA_CODEHASH: &[u8; 66] = #[no_mangle] pub unsafe extern "C" fn msg_sender(sender: *mut u8) { let msg_sender = - Context::current().get_msg_sender().expect("msg_sender should be set"); + Context::current().msg_sender().expect("msg_sender should be set"); std::ptr::copy(msg_sender.as_ptr(), sender, 20); } @@ -170,7 +170,7 @@ pub unsafe extern "C" fn msg_value(value: *mut u8) { #[no_mangle] pub unsafe extern "C" fn contract_address(address: *mut u8) { let contract_address = Context::current() - .get_contract_address() + .contract_address() .expect("contract_address should be set"); std::ptr::copy(contract_address.as_ptr(), address, 20); } @@ -236,7 +236,7 @@ pub unsafe extern "C" fn account_codehash(address: *const u8, dest: *mut u8) { /// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d #[no_mangle] pub unsafe extern "C" fn return_data_size() -> usize { - Context::current().get_return_data_size() + Context::current().return_data_size() } /// Copies the bytes of the last EVM call or deployment return result. From bc9cf09628675da95d2a9b91b195f1db640d85f6 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 23 Jan 2025 09:54:32 +0400 Subject: [PATCH 33/42] update docs --- crates/motsu/src/context.rs | 12 ++++++++---- crates/motsu/src/router.rs | 8 ++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index f8555b8..db63310 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -12,12 +12,16 @@ use crate::{ router::{RouterContext, TestRouter}, }; -/// Storage mock: A global mutable key-value store. -/// Allows concurrent access. +/// Storage mock. +/// +/// A global mutable key-value store that allows concurrent access. /// /// The key is the test [`Context`], an id of the test thread. /// /// The value is the [`MockStorage`], a storage of the test case. +/// +/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is accessed +/// twice from the same thread. static STORAGE: Lazy> = Lazy::new(DashMap::new); /// Context of stylus unit tests associated with the current test thread. @@ -73,12 +77,12 @@ impl Context { .insert(key, value); } - /// Set the message sender account address. + /// Set the message sender address. fn set_msg_sender(self, msg_sender: Address) -> Option
{ self.storage().msg_sender.replace(msg_sender) } - /// Get the message sender account address. + /// Get the message sender address. #[must_use] pub fn msg_sender(self) -> Option
{ self.storage().msg_sender diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index 255fb10..6f21f00 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -14,8 +14,9 @@ use stylus_sdk::{ ArbResult, }; -/// Router Storage: A global mutable key-value store. -/// Allows concurrent access. +/// Router Storage. +/// +/// A global mutable key-value store that allows concurrent access. /// /// The key is the [`RouterContext`], a combination of [`ThreadId`] and /// [`Address`] to avoid a deadlock, while calling more than two contracts @@ -23,6 +24,9 @@ use stylus_sdk::{ /// /// The value is the [`RouterStorage`], a router of the contract generated by /// `stylus-sdk`. +/// +/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is accessed +/// twice from the same thread. static ROUTER_STORAGE: Lazy> = Lazy::new(DashMap::new); From 28f1d22d0a060f755b2b84ba34575c730d84103d Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 23 Jan 2025 09:58:49 +0400 Subject: [PATCH 34/42] ++ --- crates/motsu/src/context.rs | 4 ++-- crates/motsu/src/router.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index db63310..d312f82 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -20,8 +20,8 @@ use crate::{ /// /// The value is the [`MockStorage`], a storage of the test case. /// -/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is accessed -/// twice from the same thread. +/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is +/// accessed twice from the same thread. static STORAGE: Lazy> = Lazy::new(DashMap::new); /// Context of stylus unit tests associated with the current test thread. diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index 6f21f00..ce2bc88 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -15,7 +15,7 @@ use stylus_sdk::{ }; /// Router Storage. -/// +/// /// A global mutable key-value store that allows concurrent access. /// /// The key is the [`RouterContext`], a combination of [`ThreadId`] and @@ -25,8 +25,8 @@ use stylus_sdk::{ /// The value is the [`RouterStorage`], a router of the contract generated by /// `stylus-sdk`. /// -/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is accessed -/// twice from the same thread. +/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is +/// accessed twice from the same thread. static ROUTER_STORAGE: Lazy> = Lazy::new(DashMap::new); From 2766a4367677187a69cd0fb2f95669bb39a9aa87 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 23 Jan 2025 09:59:59 +0400 Subject: [PATCH 35/42] ++ --- crates/motsu/src/context.rs | 4 ++-- crates/motsu/src/router.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index d312f82..8d7aadb 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -20,8 +20,8 @@ use crate::{ /// /// The value is the [`MockStorage`], a storage of the test case. /// -/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is -/// accessed twice from the same thread. +/// NOTE: The [`DashMap`] will deadlock execution, when the same key is +/// accessed twice from the same thread. static STORAGE: Lazy> = Lazy::new(DashMap::new); /// Context of stylus unit tests associated with the current test thread. diff --git a/crates/motsu/src/router.rs b/crates/motsu/src/router.rs index ce2bc88..f12ea20 100644 --- a/crates/motsu/src/router.rs +++ b/crates/motsu/src/router.rs @@ -25,8 +25,8 @@ use stylus_sdk::{ /// The value is the [`RouterStorage`], a router of the contract generated by /// `stylus-sdk`. /// -/// NOTE: The [`DashMap`] will deadlock execution, when the same bucket is -/// accessed twice from the same thread. +/// NOTE: The [`DashMap`] will deadlock execution, when the same key is +/// accessed twice from the same thread. static ROUTER_STORAGE: Lazy> = Lazy::new(DashMap::new); From 8f662c874b7f7f0cf2dbe78bb95768cd448b9275 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 31 Jan 2025 18:29:45 +0400 Subject: [PATCH 36/42] rename `contract` field to `storage` --- crates/motsu/src/context.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 8d7aadb..98991c9 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -285,7 +285,7 @@ type ContractStorage = HashMap; /// Contract call entity, related to the contract type `ST` and the caller's /// account. pub struct ContractCall { - contract: ST, + storage: ST, caller_address: Address, contract_address: Address, } @@ -309,7 +309,7 @@ impl ::core::ops::Deref for ContractCall { #[inline] fn deref(&self) -> &Self::Target { self.set_call_params(); - &self.contract + &self.storage } } @@ -317,7 +317,7 @@ impl ::core::ops::DerefMut for ContractCall { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.set_call_params(); - &mut self.contract + &mut self.storage } } @@ -380,7 +380,7 @@ impl Contract { #[must_use] pub fn sender>(&self, account: A) -> ContractCall { ContractCall { - contract: unsafe { ST::new(uint!(0_U256), 0) }, + storage: unsafe { ST::new(uint!(0_U256), 0) }, caller_address: account.into(), contract_address: self.address, } From ada172f1c14105ac5951f033505cce8b4ab5c52e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sat, 1 Feb 2025 16:49:14 +0400 Subject: [PATCH 37/42] update changelog --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ae2b6..8b2371e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Mocks for the `msg::sender()` #14 +- Mocks for the external contract calls. + Two and more contracts can be injected into test #14 +- Option to inject `Account` or `Address` in the test #14 + +### Changed (Breaking) + +- `Contract<..>` wrapper is mandatory for the contract's argument #14 +- To call contract's function it is mandatory to explicitly indicate the sender, + through `Contract::<..>::sender(..)` function #14 +- `Contract`'s `T` type should implement `TopLevelStorage` #14 + ## [0.3.0] - 2025-01-07 ### Changed (Breaking) From d3e60a53e85d556ff81491c22d5fefd664366408 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sat, 1 Feb 2025 20:17:16 +0400 Subject: [PATCH 38/42] prevent Contract<..> drop restricting ContractCall<..> with reference --- crates/motsu/src/context.rs | 22 ++++++++++++++-------- crates/motsu/src/lib.rs | 13 +++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 98991c9..293a00f 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -284,26 +284,32 @@ type ContractStorage = HashMap; /// Contract call entity, related to the contract type `ST` and the caller's /// account. -pub struct ContractCall { +pub struct ContractCall<'a, ST: StorageType> { storage: ST, caller_address: Address, - contract_address: Address, + /// We need to hold a reference to [`Contract`], because + /// `Contract::::new().sender(alice)` can accidentally drop + /// [`Contract`]. + /// + /// With `contract_ref` code like: `Contract::::new().sender(alice)` + /// will not compile. + contract_ref: &'a Contract, } -impl ContractCall { +impl<'a, ST: StorageType> ContractCall<'a, ST> { /// Get the contract's address. pub fn address(&self) -> Address { - self.contract_address + self.contract_ref.address } /// Preset the call parameters. fn set_call_params(&self) { let _ = Context::current().set_msg_sender(self.caller_address); - let _ = Context::current().set_contract_address(self.contract_address); + let _ = Context::current().set_contract_address(self.address()); } } -impl ::core::ops::Deref for ContractCall { +impl<'a, ST: StorageType> ::core::ops::Deref for ContractCall<'a, ST> { type Target = ST; #[inline] @@ -313,7 +319,7 @@ impl ::core::ops::Deref for ContractCall { } } -impl ::core::ops::DerefMut for ContractCall { +impl<'a, ST: StorageType> ::core::ops::DerefMut for ContractCall<'a, ST> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.set_call_params(); @@ -382,7 +388,7 @@ impl Contract { ContractCall { storage: unsafe { ST::new(uint!(0_U256), 0) }, caller_address: account.into(), - contract_address: self.address, + contract_ref: self, } } } diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index d87efb5..369437b 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -193,6 +193,19 @@ mod ping_pong_tests { assert_eq!(ping.sender(alice).contract_address.get(), ping.address()); assert_eq!(pong.sender(alice).contract_address.get(), pong.address()); } + + #[motsu_proc::test] + fn contract_should_not_drop() { + let alice = Account::random(); + let ping = Contract::::new(); + let mut ping = ping.sender(alice); + let pong = Contract::::new(); + let pong = pong.sender(alice); + + let _ = ping + .ping(pong.address(), uint!(10_U256)) + .expect("contract ping should not drop"); + } } #[cfg(test)] From 74e16fa04470c50f545d12eea3cf92838a5c47a6 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sat, 1 Feb 2025 23:56:56 +0400 Subject: [PATCH 39/42] add static call + test --- crates/motsu/src/lib.rs | 23 +++++++++++++++++++++++ crates/motsu/src/shims.rs | 36 ++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 369437b..888b18b 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -96,6 +96,12 @@ mod ping_pong_tests { Ok(value) } + fn can_ping(&mut self, to: Address) -> bool { + let receiver = IPongContract::new(to); + let call = Call::new_in(self); + receiver.can_pong(call).expect("should pong successfully") + } + fn has_pong(&self, to: Address) -> bool { to.has_code() } @@ -107,6 +113,9 @@ mod ping_pong_tests { interface IPongContract { #[allow(missing_docs)] function pong(uint256 value) external returns (uint256); + + #[allow(missing_docs)] + function canPong() external view returns (bool); } } @@ -117,6 +126,7 @@ mod ping_pong_tests { contract_address: StorageAddress, } + // TODO#q: test error and panic with magic value #[public] impl PongContract { fn pong(&mut self, value: U256) -> Result> { @@ -128,6 +138,10 @@ mod ping_pong_tests { Ok(value + uint!(1_U256)) } + + fn can_pong(&self) -> bool { + true + } } unsafe impl TopLevelStorage for PongContract {} @@ -149,6 +163,15 @@ mod ping_pong_tests { assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); } + #[motsu_proc::test] + fn external_static_call( + ping: Contract, + pong: Contract, + alice: Account, + ) { + assert!(ping.sender(alice).can_ping(pong.address())); + } + #[motsu_proc::test] fn msg_sender( ping: Contract, diff --git a/crates/motsu/src/shims.rs b/crates/motsu/src/shims.rs index eac979f..95c639a 100644 --- a/crates/motsu/src/shims.rs +++ b/crates/motsu/src/shims.rs @@ -304,16 +304,18 @@ pub unsafe extern "C" fn call_contract( /// [`STATIC_CALL`]: https://www.evm.codes/#FA #[no_mangle] pub unsafe extern "C" fn static_call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, + contract: *const u8, + calldata: *const u8, + calldata_len: usize, _gas: u64, - _return_data_len: *mut usize, + return_data_len: *mut usize, ) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 + Context::current().call_contract_raw( + contract, + calldata, + calldata_len, + return_data_len, + ) } /// Delegate calls the contract at the given address, with the option to limit @@ -332,16 +334,18 @@ pub unsafe extern "C" fn static_call_contract( /// [`DELEGATE_CALL`]: https://www.evm.codes/#F4 #[no_mangle] pub unsafe extern "C" fn delegate_call_contract( - _contract: *const u8, - _calldata: *const u8, - _calldata_len: usize, + contract: *const u8, + calldata: *const u8, + calldata_len: usize, _gas: u64, - _return_data_len: *mut usize, + return_data_len: *mut usize, ) -> u8 { - // TODO: #156 - // No-op: we do not use this function in our unit-tests, - // but the binary does include it. - 0 + Context::current().call_contract_raw( + contract, + calldata, + calldata_len, + return_data_len, + ) } /// Gets a bounded estimate of the Unix timestamp at which the Sequencer From f1a5e7823c4e8e340b1b2d95b892d16ffcef4e36 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sun, 2 Feb 2025 00:25:40 +0400 Subject: [PATCH 40/42] add error call test --- crates/motsu/src/lib.rs | 59 ++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index 888b18b..dae18da 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -62,14 +62,9 @@ extern crate alloc; mod ping_pong_tests { #![deny(rustdoc::broken_intra_doc_links)] use alloy_primitives::uint; - use stylus_sdk::{ - alloy_primitives::{Address, U256}, - call::Call, - contract, msg, - prelude::{public, storage, AddressVM, TopLevelStorage}, - storage::{StorageAddress, StorageU256}, - }; - + use alloy_sol_types::{sol, SolError}; + use stylus_sdk::{alloy_primitives::{Address, U256}, call, call::Call, contract, msg, prelude::{public, storage, AddressVM, TopLevelStorage}, storage::{StorageAddress, StorageU256}}; + use stylus_sdk::prelude::SolidityError; use crate::context::{Account, Contract}; #[storage] @@ -84,8 +79,7 @@ mod ping_pong_tests { fn ping(&mut self, to: Address, value: U256) -> Result> { let receiver = IPongContract::new(to); let call = Call::new_in(self); - let value = - receiver.pong(call, value).expect("should pong successfully"); + let value = receiver.pong(call, value)?; let pings_count = self.pings_count.get(); self.pings_count.set(pings_count + uint!(1_U256)); @@ -96,10 +90,10 @@ mod ping_pong_tests { Ok(value) } - fn can_ping(&mut self, to: Address) -> bool { + fn can_ping(&mut self, to: Address) -> Result> { let receiver = IPongContract::new(to); let call = Call::new_in(self); - receiver.can_pong(call).expect("should pong successfully") + Ok(receiver.can_pong(call)?) } fn has_pong(&self, to: Address) -> bool { @@ -119,6 +113,19 @@ mod ping_pong_tests { } } + sol! { + #[derive(Debug)] + #[allow(missing_docs)] + error MagicError(uint256 value); + } + + #[derive(SolidityError, Debug)] + pub enum PongError { + MagicError(MagicError), + } + + const MAGIC_ERROR_VALUE: U256 = uint!(42_U256); + #[storage] struct PongContract { pongs_count: StorageU256, @@ -126,10 +133,13 @@ mod ping_pong_tests { contract_address: StorageAddress, } - // TODO#q: test error and panic with magic value #[public] impl PongContract { - fn pong(&mut self, value: U256) -> Result> { + fn pong(&mut self, value: U256) -> Result { + if value == MAGIC_ERROR_VALUE { + return Err(PongError::MagicError(MagicError { value })); + } + let pongs_count = self.pongs_count.get(); self.pongs_count.set(pongs_count + uint!(1_U256)); @@ -162,6 +172,21 @@ mod ping_pong_tests { assert_eq!(ping.sender(alice).pings_count.get(), uint!(1_U256)); assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); } + + #[motsu_proc::test] + fn external_call_error( + ping: Contract, + pong: Contract, + alice: Account, + ) { + let value = MAGIC_ERROR_VALUE; + let err = ping + .sender(alice) + .ping(pong.address(), value) + .expect_err("should fail to ping"); + + assert_eq!(err, MagicError { value }.abi_encode()); + } #[motsu_proc::test] fn external_static_call( @@ -169,7 +194,11 @@ mod ping_pong_tests { pong: Contract, alice: Account, ) { - assert!(ping.sender(alice).can_ping(pong.address())); + let can_ping = ping + .sender(alice) + .can_ping(pong.address()) + .expect("should ping successfully"); + assert!(can_ping); } #[motsu_proc::test] From efc7e734e3e481f67e8b39790a6a83b6aa13d6c6 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sun, 2 Feb 2025 00:27:42 +0400 Subject: [PATCH 41/42] elide lifetimes --- crates/motsu/src/context.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index 293a00f..fa538b7 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -296,7 +296,7 @@ pub struct ContractCall<'a, ST: StorageType> { contract_ref: &'a Contract, } -impl<'a, ST: StorageType> ContractCall<'a, ST> { +impl ContractCall<'_, ST> { /// Get the contract's address. pub fn address(&self) -> Address { self.contract_ref.address @@ -309,7 +309,7 @@ impl<'a, ST: StorageType> ContractCall<'a, ST> { } } -impl<'a, ST: StorageType> ::core::ops::Deref for ContractCall<'a, ST> { +impl ::core::ops::Deref for ContractCall<'_, ST> { type Target = ST; #[inline] @@ -319,7 +319,7 @@ impl<'a, ST: StorageType> ::core::ops::Deref for ContractCall<'a, ST> { } } -impl<'a, ST: StorageType> ::core::ops::DerefMut for ContractCall<'a, ST> { +impl ::core::ops::DerefMut for ContractCall<'_, ST> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.set_call_params(); From 7b330c39050c118839e7afa6a86a935a78aae4cd Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Sun, 2 Feb 2025 00:29:20 +0400 Subject: [PATCH 42/42] fix fmt --- crates/motsu/src/context.rs | 2 +- crates/motsu/src/lib.rs | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/motsu/src/context.rs b/crates/motsu/src/context.rs index fa538b7..c742bbc 100644 --- a/crates/motsu/src/context.rs +++ b/crates/motsu/src/context.rs @@ -290,7 +290,7 @@ pub struct ContractCall<'a, ST: StorageType> { /// We need to hold a reference to [`Contract`], because /// `Contract::::new().sender(alice)` can accidentally drop /// [`Contract`]. - /// + /// /// With `contract_ref` code like: `Contract::::new().sender(alice)` /// will not compile. contract_ref: &'a Contract, diff --git a/crates/motsu/src/lib.rs b/crates/motsu/src/lib.rs index dae18da..620f5a6 100644 --- a/crates/motsu/src/lib.rs +++ b/crates/motsu/src/lib.rs @@ -48,6 +48,8 @@ //! ``` //! //! [test_attribute]: crate::test +#[cfg(test)] +extern crate alloc; mod context; pub mod prelude; mod router; @@ -55,16 +57,19 @@ mod shims; pub use motsu_proc::test; -#[cfg(test)] -extern crate alloc; - #[cfg(test)] mod ping_pong_tests { #![deny(rustdoc::broken_intra_doc_links)] use alloy_primitives::uint; use alloy_sol_types::{sol, SolError}; - use stylus_sdk::{alloy_primitives::{Address, U256}, call, call::Call, contract, msg, prelude::{public, storage, AddressVM, TopLevelStorage}, storage::{StorageAddress, StorageU256}}; - use stylus_sdk::prelude::SolidityError; + use stylus_sdk::{ + alloy_primitives::{Address, U256}, + call::Call, + contract, msg, + prelude::{public, storage, AddressVM, SolidityError, TopLevelStorage}, + storage::{StorageAddress, StorageU256}, + }; + use crate::context::{Account, Contract}; #[storage] @@ -139,7 +144,7 @@ mod ping_pong_tests { if value == MAGIC_ERROR_VALUE { return Err(PongError::MagicError(MagicError { value })); } - + let pongs_count = self.pongs_count.get(); self.pongs_count.set(pongs_count + uint!(1_U256)); @@ -172,7 +177,7 @@ mod ping_pong_tests { assert_eq!(ping.sender(alice).pings_count.get(), uint!(1_U256)); assert_eq!(pong.sender(alice).pongs_count.get(), uint!(1_U256)); } - + #[motsu_proc::test] fn external_call_error( ping: Contract, @@ -184,7 +189,7 @@ mod ping_pong_tests { .sender(alice) .ping(pong.address(), value) .expect_err("should fail to ping"); - + assert_eq!(err, MagicError { value }.abi_encode()); }