diff --git a/.github/workflows/gas-test.yml b/.github/workflows/gas-test.yml index 3b2ecd5222..900c4a2247 100644 --- a/.github/workflows/gas-test.yml +++ b/.github/workflows/gas-test.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - toolchain: ["1.92", "1.85"] + toolchain: ["1.95", "1.85"] steps: - name: Checkout code uses: actions/checkout@v5 @@ -46,7 +46,7 @@ jobs: with: toolchain: ${{ matrix.toolchain }} - - name: Run gas tests (prod) + - name: Run gas tests (experimental) run: | cd contracts/feature-tests/gas-tests cargo test --features wasmer-experimental diff --git a/.github/workflows/interactor-tests-ledger.yml b/.github/workflows/interactor-tests-ledger.yml new file mode 100644 index 0000000000..1a84501f6b --- /dev/null +++ b/.github/workflows/interactor-tests-ledger.yml @@ -0,0 +1,65 @@ +name: Interactor Tests, with ledger mock + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +jobs: + adder-ledger-interactor-test: + name: Adder ledger interactor test (chain simulator) + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.96 + target: wasm32v1-none + + - name: Install libudev (required by hidapi/ledger feature) + run: sudo apt-get update && sudo apt-get install -y libudev-dev + + - name: Install sc-meta + run: cargo install --path framework/meta --locked + + - name: Install wasm-opt + run: cargo install wasm-opt + + - name: Build adder contract + env: + RUSTFLAGS: "" + run: sc-meta all build --path contracts/examples/adder + + - name: Pull chain simulator docker image + env: + RUSTFLAGS: "" + run: sc-meta cs install + + - name: Start chain simulator + env: + RUSTFLAGS: "" + run: sc-meta cs start & + + - name: Wait for chain simulator to be ready + run: sleep 5 + + - name: Run ledger interactor test + env: + RUSTFLAGS: "" + run: > + cargo test + -p adder-interactor + --test adder_interactor_ledger_test + --features ledger,chain-simulator-tests + + - name: Stop chain simulator + if: always() + env: + RUSTFLAGS: "" + run: sc-meta cs stop diff --git a/Cargo.lock b/Cargo.lock index c9a0dd65ac..b475e70503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ dependencies = [ name = "abi-tester" version = "0.0.0" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "multiversx-sc", "multiversx-sc-meta-lib", "multiversx-sc-scenario", @@ -38,6 +38,19 @@ dependencies = [ "multiversx-sc-scenario", ] +[[package]] +name = "adder-interactor" +version = "0.0.0" +dependencies = [ + "adder", + "clap", + "multiversx-sc-snippets", + "serde", + "serial_test", + "tokio", + "toml", +] + [[package]] name = "adder-meta" version = "0.0.0" @@ -161,9 +174,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -171,6 +190,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -185,9 +215,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "zeroize", @@ -195,14 +225,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -264,24 +295,11 @@ dependencies = [ "multiversx-sc-meta-lib", ] -[[package]] -name = "basic-interactor" -version = "0.0.0" -dependencies = [ - "adder", - "clap", - "multiversx-sc-snippets", - "serde", - "serial_test", - "tokio", - "toml", -] - [[package]] name = "bech32" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" +checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8" [[package]] name = "benchmark-common" @@ -322,7 +340,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -333,7 +351,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -351,9 +369,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "hex-conservative", ] @@ -366,9 +384,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "blake2" @@ -390,9 +408,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -416,12 +434,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", - "serde", + "serde_core", ] [[package]] @@ -488,7 +506,7 @@ checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -499,15 +517,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -538,9 +556,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -554,7 +572,6 @@ dependencies = [ "multiversx-sc", "multiversx-sc-modules", "multiversx-sc-scenario", - "num-bigint", ] [[package]] @@ -588,7 +605,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "crypto-common 0.2.2", "inout", ] @@ -632,10 +649,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1016,7 +1033,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1039,7 +1056,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1050,7 +1067,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1067,6 +1084,38 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "delegation-sc-interact" version = "0.0.0" @@ -1096,7 +1145,7 @@ checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1118,7 +1167,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -1139,7 +1188,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", @@ -1169,9 +1218,15 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] +[[package]] +name = "doc-comment" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + [[package]] name = "dunce" version = "1.0.5" @@ -1184,13 +1239,13 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d36219658beb39702975c707dee7895943ca281ca46eebbc5ea395171b9c182b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "byteorder", "lazy_static", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1202,7 +1257,7 @@ dependencies = [ "byteorder", "dynasm", "fnv", - "memmap2 0.9.10", + "memmap2 0.9.11", ] [[package]] @@ -1241,7 +1296,6 @@ version = "0.0.0" dependencies = [ "multiversx-sc", "multiversx-sc-scenario", - "num-bigint", ] [[package]] @@ -1305,14 +1359,14 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -1320,9 +1374,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -1573,12 +1627,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1780,7 +1828,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1869,17 +1917,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", "wasm-bindgen", ] @@ -2032,7 +2078,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils 0.3.0", - "http 1.4.1", + "http 1.4.2", "js-sys", "pin-project", "serde", @@ -2134,7 +2180,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2150,18 +2196,34 @@ dependencies = [ "toml", ] +[[package]] +name = "greeter" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-scenario", +] + +[[package]] +name = "greeter-meta" +version = "0.0.0" +dependencies = [ + "greeter", + "multiversx-sc-meta-lib", +] + [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.1", + "http 1.4.2", "indexmap", "slab", "tokio", @@ -2178,26 +2240,23 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2231,6 +2290,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +[[package]] +name = "hidapi" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "winapi", +] + [[package]] name = "hmac" version = "0.13.0" @@ -2253,9 +2324,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2268,7 +2339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.1", + "http 1.4.2", ] [[package]] @@ -2279,7 +2350,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -2292,9 +2363,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -2310,7 +2381,7 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.4.1", + "http 1.4.2", "http-body", "httparse", "itoa", @@ -2326,7 +2397,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.1", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -2345,7 +2416,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "hyper", "ipnet", @@ -2441,12 +2512,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2476,9 +2541,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" dependencies = [ "crossbeam-deque", "globset", @@ -2507,7 +2572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "699c1b6d335e63d0ba5c1e1c7f647371ce989c3bcbe1f7ed2b85fa56e3bd1a21" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2554,9 +2619,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" dependencies = [ "either", ] @@ -2569,10 +2634,11 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -2582,13 +2648,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2618,7 +2684,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2637,7 +2703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2652,13 +2718,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2759,9 +2824,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" [[package]] name = "leb128fmt" @@ -2769,6 +2834,43 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "ledger-apdu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" +dependencies = [ + "arrayref", + "no-std-compat", + "snafu", +] + +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport-hid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee" +dependencies = [ + "byteorder", + "cfg-if", + "hex", + "hidapi", + "ledger-transport", + "libc", + "log", + "thiserror 1.0.69", +] + [[package]] name = "libc" version = "0.2.186" @@ -2847,9 +2949,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lottery" @@ -2957,9 +3059,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -2972,9 +3074,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -3115,12 +3217,16 @@ checksum = "049721bdb4abb82202cf8d9b7981ef0c080615b7cb4ef2de345694a5329f1216" name = "multiversx-chain-core" version = "0.23.1" dependencies = [ + "base64", "bech32", - "bitflags 2.11.1", + "bitflags 2.13.0", "blake2", + "ed25519-dalek", "hex", + "hex-literal", "multiversx-sc-codec", "serde", + "serde_json", "sha2 0.11.0", "sha3", ] @@ -3143,11 +3249,10 @@ name = "multiversx-chain-vm" version = "0.23.1" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "colored 3.1.1", - "ed25519-dalek", "hex", - "itertools 0.14.0", + "itertools 0.15.0", "log", "multiversx-bls", "multiversx-chain-core", @@ -3189,7 +3294,7 @@ dependencies = [ name = "multiversx-price-aggregator-sc" version = "0.66.2" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "multiversx-sc", "multiversx-sc-modules", "multiversx-sc-scenario", @@ -3209,7 +3314,7 @@ dependencies = [ name = "multiversx-sc" version = "0.66.2" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "generic-array 1.4.3", "multiversx-chain-core", "multiversx-sc-codec", @@ -3223,7 +3328,7 @@ name = "multiversx-sc-codec" version = "0.25.0" dependencies = [ "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "multiversx-sc-codec-derive", "num-bigint", "num-traits", @@ -3237,7 +3342,7 @@ dependencies = [ "hex", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3256,7 +3361,7 @@ dependencies = [ "proc-macro2", "quote", "radix_trie", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3264,7 +3369,6 @@ name = "multiversx-sc-meta" version = "0.66.2" dependencies = [ "anyhow", - "base64", "bip39", "clap", "colored 3.1.1", @@ -3280,8 +3384,10 @@ dependencies = [ "multiversx-sc-snippets", "multiversx-sdk", "pathdiff", + "rand 0.10.1", "regex", "reqwest", + "rpassword", "ruplacer", "semver", "serde", @@ -3289,6 +3395,7 @@ dependencies = [ "serial_test", "tokio", "toml", + "uuid", "zip", ] @@ -3308,7 +3415,7 @@ dependencies = [ "serde", "serde_json", "toml", - "wasmparser 0.249.0", + "wasmparser 0.252.0", "wasmprinter", "wat", ] @@ -3327,7 +3434,7 @@ dependencies = [ "colored 3.1.1", "hex", "hex-literal", - "itertools 0.14.0", + "itertools 0.15.0", "log", "multiversx-chain-scenario-format", "multiversx-chain-vm", @@ -3359,8 +3466,12 @@ dependencies = [ "multiversx-sdk", "multiversx-sdk-dapp", "multiversx-sdk-http", + "pathdiff", + "serde", "serde_json", + "serial_test", "tokio", + "toml", ] [[package]] @@ -3376,25 +3487,24 @@ version = "0.16.1" dependencies = [ "aes", "anyhow", - "base64", "bip39", + "bitflags 2.13.0", "ctr", "hex", "hmac", - "itertools 0.14.0", + "itertools 0.15.0", + "ledger-apdu", + "ledger-transport-hid", "log", "multiversx-chain-core", "pbkdf2", "pem", - "rand 0.10.1", - "rpassword", "scrypt", "serde", "serde_json", "serde_repr", "sha2 0.11.0", "sha3", - "uuid", "zeroize", ] @@ -3403,10 +3513,10 @@ name = "multiversx-sdk-dapp" version = "0.16.1" dependencies = [ "anyhow", - "getrandom 0.4.2", + "getrandom 0.4.3", "gloo-net 0.7.0", "hex", - "itertools 0.14.0", + "itertools 0.15.0", "js-sys", "log", "multiversx-sdk", @@ -3421,7 +3531,7 @@ version = "0.16.1" dependencies = [ "anyhow", "hex", - "itertools 0.14.0", + "itertools 0.15.0", "log", "multiversx-sdk", "reqwest", @@ -3464,7 +3574,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3536,6 +3646,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nom" version = "7.1.3" @@ -3798,7 +3914,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3871,6 +3987,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -3911,7 +4033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -3967,7 +4089,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4065,7 +4187,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4087,9 +4209,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -4107,9 +4229,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -4143,9 +4265,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -4209,7 +4331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -4322,14 +4444,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -4350,9 +4472,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "region" @@ -4388,7 +4510,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 1.4.1", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -4475,7 +4597,7 @@ checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4591,7 +4713,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -4600,9 +4722,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "once_cell", @@ -4614,9 +4736,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -4781,7 +4903,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4881,7 +5003,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4906,7 +5028,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -4952,7 +5074,7 @@ checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5099,9 +5221,31 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "socket2" @@ -5147,7 +5291,6 @@ version = "0.0.0" dependencies = [ "multiversx-sc", "multiversx-sc-scenario", - "num-bigint", ] [[package]] @@ -5158,6 +5301,22 @@ dependencies = [ "std-contract", ] +[[package]] +name = "storage-examples" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-scenario", +] + +[[package]] +name = "storage-examples-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "storage-examples", +] + [[package]] name = "str-repeat" version = "0.0.0" @@ -5206,9 +5365,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -5232,7 +5391,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5241,7 +5400,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5319,7 +5478,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5330,7 +5489,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5418,7 +5577,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5549,10 +5708,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "bytes", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", "tower", @@ -5592,7 +5751,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -5667,9 +5826,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -5745,11 +5904,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -5831,6 +5990,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wallet-miner" +version = "0.1.0" +dependencies = [ + "anyhow", + "multiversx-sc-meta", + "multiversx-sdk", +] + [[package]] name = "want" version = "0.3.1" @@ -5852,23 +6020,14 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen 0.46.0", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -5879,9 +6038,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -5889,9 +6048,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5899,56 +6058,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" -version = "0.244.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.251.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a879a421bd17c528b74721b2abf4c62e8f1d1889c2ba8c3c50d02deaf2ce395" -dependencies = [ - "leb128fmt", - "wasmparser 0.251.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasmparser 0.252.0", ] [[package]] @@ -6100,74 +6237,51 @@ version = "0.224.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f17a5917c2ddd3819e84c661fae0d6ba29d7b9c1f0e96c708c65a9c4188e11" dependencies = [ - "bitflags 2.11.1", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap", - "semver", + "bitflags 2.13.0", ] [[package]] name = "wasmparser" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.17.1", "indexmap", "semver", "serde", ] -[[package]] -name = "wasmparser" -version = "0.251.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437970b35b1a85cfde9c74b2398352d8d653f3bd8e3a3db0c063ea8f5b4b36ff" -dependencies = [ - "bitflags 2.11.1", - "indexmap", - "semver", -] - [[package]] name = "wasmprinter" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e227e800fc74c0d60117391f71d60e4c956333cfc3be796ae72d269002aa52" +checksum = "7142797de29b35ab8dbf15c00f55fda75d409da4c423a8ab8bd6b667a785824b" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.249.0", + "wasmparser 0.252.0", ] [[package]] name = "wast" -version = "251.0.0" +version = "252.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7467dda0a96142eb2c980329dfb62480b1e1d3622fdeb1a44e2bca6ceed74" +checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.251.0", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.251.0" +version = "1.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b1086c9e85b95bd6a229a928bc6c6d0662e42af0250c88d067b418831ea4d4" +checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" dependencies = [ "wast", ] @@ -6182,9 +6296,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -6202,13 +6316,29 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -6218,6 +6348,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" @@ -6505,94 +6641,6 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", -] - [[package]] name = "writeable" version = "0.6.3" @@ -6651,7 +6699,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6681,14 +6729,14 @@ checksum = "8399f1d134ab8e69abc7cded19f114621d520bd9c79c5a0f34091bf664d7325b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -6703,28 +6751,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6744,15 +6792,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -6784,7 +6832,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -6803,9 +6851,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index c4f22840d7..49e4c43ed3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "tools/mxpy-snippet-generator", "tools/op-test-gen", "tools/strip-path", + "tools/wallet-miner", "tools/wat-gen", "contracts/modules", @@ -93,6 +94,8 @@ members = [ "contracts/examples/factorial/meta", "contracts/examples/fractional-nfts", "contracts/examples/fractional-nfts/meta", + "contracts/examples/greeter", + "contracts/examples/greeter/meta", "contracts/examples/lottery", "contracts/examples/lottery/meta", "contracts/examples/multisig", @@ -118,6 +121,8 @@ members = [ "contracts/examples/rewards-distribution/meta", "contracts/examples/seed-nft-minter", "contracts/examples/seed-nft-minter/meta", + "contracts/examples/storage-examples", + "contracts/examples/storage-examples/meta", "contracts/examples/token-release", "contracts/examples/token-release/meta", @@ -211,3 +216,23 @@ members = [ # "chain/wasmer-prod", ## not included, references wasmer 2.2 # "contracts/feature-tests/gas-tests", ## uncomment for easier development ] + +[workspace.dependencies.multiversx-sc] +version = "0.66.2" +path = "framework/base" + +[workspace.dependencies.multiversx-sc-scenario] +version = "0.66.2" +path = "framework/scenario" + +[workspace.dependencies.multiversx-sc-meta-lib] +version = "0.66.2" +path = "framework/meta-lib" + +[workspace.dependencies.multiversx-sc-snippets] +version = "0.66.2" +path = "framework/snippets" + +[workspace.dependencies.multiversx-sc-modules] +version = "0.66.2" +path = "contracts/modules" diff --git a/chain/core/Cargo.toml b/chain/core/Cargo.toml index 7cb4cd3137..26ef3f099b 100644 --- a/chain/core/Cargo.toml +++ b/chain/core/Cargo.toml @@ -17,18 +17,24 @@ keywords = ["multiversx", "blockchain", "vm", "tools"] categories = ["cryptography::cryptocurrencies", "development-tools::debugging"] [features] -std = ["bech32", "serde", "hex", "blake2", "sha2", "sha3"] +std = ["base64", "bech32", "serde", "hex", "blake2", "sha2", "sha3", "ed25519-dalek"] [dependencies] -bitflags = "2.9" -bech32 = { version = "0.11", optional = true } +bitflags = "2.13" +base64 = { version = "0.22", optional = true } +bech32 = { version = "0.12", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } hex = { version = "0.4", optional = true } blake2 = { version = "0.10", optional = true } sha2 = { version = "0.11", optional = true } sha3 = { version = "0.12", optional = true } +ed25519-dalek = { version = "2.1", optional = true } [dependencies.multiversx-sc-codec] version = "=0.25.0" path = "../../data/codec" features = ["derive"] + +[dev-dependencies] +serde_json = "1.0" +hex-literal = "1.0" diff --git a/chain/core/src/std.rs b/chain/core/src/std.rs index d5863e7703..a0fd170436 100644 --- a/chain/core/src/std.rs +++ b/chain/core/src/std.rs @@ -1,7 +1,9 @@ +mod base64; mod bech32_address; mod code_hash; pub mod crypto; pub mod new_address; -pub use bech32_address::Bech32Address; +pub use base64::{base64_decode, base64_encode}; +pub use bech32_address::{Bech32Address, Bech32Hrp}; pub use code_hash::{CODE_HASH_LEN, code_hash}; diff --git a/chain/core/src/std/base64.rs b/chain/core/src/std/base64.rs new file mode 100644 index 0000000000..b76b694b26 --- /dev/null +++ b/chain/core/src/std/base64.rs @@ -0,0 +1,20 @@ +use base64::{Engine as _, engine::general_purpose}; + +/// Decodes a base64-encoded byte slice using the standard alphabet. +/// +/// Returns an error if the input contains characters outside the base64 alphabet +/// or has invalid padding. +pub fn base64_decode(to_decode: T) -> Result, base64::DecodeError> +where + T: AsRef<[u8]>, +{ + general_purpose::STANDARD.decode(to_decode) +} + +/// Encodes a byte slice as a base64 string using the standard alphabet with padding. +pub fn base64_encode(to_encode: T) -> String +where + T: AsRef<[u8]>, +{ + general_purpose::STANDARD.encode(to_encode) +} diff --git a/chain/core/src/std/bech32_address.rs b/chain/core/src/std/bech32_address.rs index 51f853f0b1..28a4fe31bc 100644 --- a/chain/core/src/std/bech32_address.rs +++ b/chain/core/src/std/bech32_address.rs @@ -2,41 +2,18 @@ use std::fmt::Display; use std::str::FromStr; use crate::{codec::*, types::Address}; -use bech32::{Bech32, Hrp}; +use bech32::Bech32; use serde::{Deserialize, Serialize}; -const BECH32_PREFIX: &str = "bech32:"; - -const ERR_ADDRESS_EMPTY: &str = "address string is empty"; +mod bech32_address_error; +pub use bech32_address_error::Bech32AddressError; -const DEFAULT_HRP: &str = "erd"; - -/// Error type returned by [`Bech32Address::try_from_bech32_string`]. -#[derive(Debug)] -pub enum Bech32AddressError { - /// The string could not be decoded as a valid bech32 value. - /// Contains a message with the offending input and the underlying error. - DecodeError(String), - /// The decoded payload is not exactly 32 bytes. - /// Contains the actual decoded length. - InvalidLength(usize), -} +mod bech32_hrp; +pub use bech32_hrp::Bech32Hrp; -impl core::fmt::Display for Bech32AddressError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Bech32AddressError::DecodeError(msg) => write!(f, "bech32 decode error: {msg}"), - Bech32AddressError::InvalidLength(len) => { - write!( - f, - "invalid address length after decoding: expected 32, got {len}" - ) - } - } - } -} +const BECH32_PREFIX: &str = "bech32:"; -impl std::error::Error for Bech32AddressError {} +const ERR_ADDRESS_EMPTY: &str = "address string is empty"; /// Wraps and address, and presents it as a bech32 expression wherever possible. /// @@ -48,7 +25,7 @@ impl std::error::Error for Bech32AddressError {} #[derive(Clone, PartialEq, Eq)] pub struct Bech32Address { pub address: Address, - pub hrp: String, + pub hrp: Bech32Hrp, pub bech32: String, } @@ -71,7 +48,7 @@ impl Bech32Address { Ok(Bech32Address { address: Address::from_slice(&dest_address_bytes), - hrp: hrp.to_string(), + hrp: Bech32Hrp::from(hrp), bech32: bech32_string, }) } @@ -95,25 +72,24 @@ impl Bech32Address { } /// Encodes an [`Address`] as bech32 using the given human-readable part (HRP). - pub fn encode_address(hrp: &str, address: Address) -> Self { - let hrp_obj = Hrp::parse(hrp).expect("invalid hrp"); - let bech32_string = - bech32::encode::(hrp_obj, address.as_bytes()).expect("bech32 encode error"); + pub fn encode_address(hrp: Bech32Hrp, address: Address) -> Self { + let bech32_string = bech32::encode::(hrp.to_inner(), address.as_bytes()) + .expect("bech32 encode error"); Bech32Address { address, - hrp: hrp.to_owned(), + hrp, bech32: bech32_string, } } /// Encodes an [`Address`] as bech32 using the default HRP (`"erd"`). pub fn encode_address_default_hrp(address: Address) -> Self { - Self::encode_address(DEFAULT_HRP, address) + Self::encode_address(Bech32Hrp::default(), address) } /// Returns the zero address encoded with the given HRP. - pub fn zero(hrp: &str) -> Self { + pub fn zero(hrp: Bech32Hrp) -> Self { Bech32Address::encode_address(hrp, Address::zero()) } @@ -164,12 +140,12 @@ impl Bech32Address { /// Returns the human-readable part (HRP) as a borrowed `&str`. pub fn as_hrp(&self) -> &str { - &self.hrp + self.hrp.as_str() } - /// Clones and returns the human-readable part (HRP) as an owned [`String`]. - pub fn to_hrp(&self) -> String { - self.hrp.clone() + /// Returns the human-readable part (HRP) as [`Bech32Hrp`]. + pub fn to_hrp(&self) -> Bech32Hrp { + self.hrp } /// Consumes `self` and returns the underlying [`Address`]. @@ -244,10 +220,10 @@ impl<'de> Deserialize<'de> for Bech32Address { { // some old interactors have it serialized like this let mut bech32 = String::deserialize(deserializer)?; - if let Some(stripped) = bech32.strip_prefix("bech32:") { + if let Some(stripped) = bech32.strip_prefix(BECH32_PREFIX) { bech32 = stripped.to_string(); } - Ok(Bech32Address::from_bech32_string(bech32)) + Bech32Address::try_from_bech32_string(bech32).map_err(serde::de::Error::custom) } } @@ -322,7 +298,7 @@ impl FromStr for Bech32Address { } Ok(Bech32Address { address: Address::from_slice(&dest_address_bytes), - hrp: hrp.to_string(), + hrp: Bech32Hrp::from(hrp), bech32: s.to_string(), }) } @@ -332,24 +308,37 @@ impl FromStr for Bech32Address { mod tests { use super::*; - // A known valid bech32 address on the MultiversX network (32-byte payload). - const VALID_BECH32: &str = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; + // Alice's address, used as a representative non-trivial test address. + const ALICE_BECH32: &str = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + // The zero address (all-zero 32-byte payload), kept for tests that specifically + // exercise that edge case. + const ZERO_BECH32: &str = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; #[test] fn test_try_from_bech32_string_valid() { - let result = Bech32Address::try_from_bech32_string(VALID_BECH32.to_string()); + let result = Bech32Address::try_from_bech32_string(ALICE_BECH32.to_string()); assert!(result.is_ok()); let addr = result.unwrap(); - assert_eq!(addr.bech32, VALID_BECH32); + assert_eq!(addr.bech32, ALICE_BECH32); assert_eq!(addr.hrp, "erd"); assert_eq!(addr.address.as_bytes().len(), 32); } + #[test] + fn test_try_from_bech32_string_zero_address() { + let result = Bech32Address::try_from_bech32_string(ZERO_BECH32.to_string()); + assert!(result.is_ok()); + let addr = result.unwrap(); + assert_eq!(addr.bech32, ZERO_BECH32); + assert!(addr.address.as_bytes().iter().all(|&b| b == 0)); + } + #[test] fn test_try_from_bech32_string_roundtrip() { - let original = Bech32Address::try_from_bech32_string(VALID_BECH32.to_string()).unwrap(); - let re_encoded = Bech32Address::encode_address(&original.hrp, original.address.clone()); - assert_eq!(re_encoded.bech32, VALID_BECH32); + let original = Bech32Address::try_from_bech32_string(ALICE_BECH32.to_string()).unwrap(); + let re_encoded = Bech32Address::encode_address(original.hrp, original.address.clone()); + assert_eq!(re_encoded.bech32, ALICE_BECH32); } #[test] @@ -369,10 +358,72 @@ mod tests { #[test] fn test_try_from_bech32_string_wrong_length() { // Encode a payload that is not 32 bytes to trigger the length check. - let hrp = bech32::Hrp::parse(DEFAULT_HRP).unwrap(); + let hrp = Bech32Hrp::default().to_inner(); let short_payload = [0u8; 10]; let bad_bech32 = bech32::encode::(hrp, &short_payload).unwrap(); let result = Bech32Address::try_from_bech32_string(bad_bech32); assert!(matches!(result, Err(Bech32AddressError::InvalidLength(10)))); } + + // --- serde Deserialize tests --- + + #[test] + fn test_deserialize_valid() { + let json = format!("\"{ALICE_BECH32}\""); + let addr: Bech32Address = serde_json::from_str(&json).unwrap(); + assert_eq!(addr.bech32, ALICE_BECH32); + } + + #[test] + fn test_deserialize_zero_address() { + let json = format!("\"{ZERO_BECH32}\""); + let addr: Bech32Address = serde_json::from_str(&json).unwrap(); + assert_eq!(addr.bech32, ZERO_BECH32); + assert!(addr.address.as_bytes().iter().all(|&b| b == 0)); + } + + #[test] + fn test_deserialize_bech32_prefix_stripped() { + // Old interactor format: "bech32:
" + let json = format!("\"bech32:{ALICE_BECH32}\""); + let addr: Bech32Address = serde_json::from_str(&json).unwrap(); + assert_eq!(addr.bech32, ALICE_BECH32); + } + + #[test] + fn test_deserialize_empty_string_returns_error() { + let result: Result = serde_json::from_str("\"\""); + assert!(result.is_err(), "expected error for empty string, got Ok"); + } + + #[test] + fn test_deserialize_invalid_bech32_returns_error() { + let result: Result = serde_json::from_str("\"not_valid!!!\""); + assert!(result.is_err(), "expected error for invalid bech32, got Ok"); + } + + #[test] + fn test_deserialize_bech32_prefix_only_returns_error() { + // "bech32:" strips to "" which should fail, not panic + let result: Result = serde_json::from_str("\"bech32:\""); + assert!(result.is_err(), "expected error for bare prefix, got Ok"); + } + + // --- serde Serialize tests --- + + #[test] + fn test_serialize_valid() { + let addr = Bech32Address::try_from_bech32_string(ALICE_BECH32.to_string()).unwrap(); + let json = serde_json::to_string(&addr).unwrap(); + assert_eq!(json, format!("\"{ALICE_BECH32}\"")); + } + + #[test] + fn test_serde_roundtrip() { + let original = Bech32Address::try_from_bech32_string(ALICE_BECH32.to_string()).unwrap(); + let json = serde_json::to_string(&original).unwrap(); + let decoded: Bech32Address = serde_json::from_str(&json).unwrap(); + assert_eq!(decoded.bech32, original.bech32); + assert_eq!(decoded.address, original.address); + } } diff --git a/chain/core/src/std/bech32_address/bech32_address_error.rs b/chain/core/src/std/bech32_address/bech32_address_error.rs new file mode 100644 index 0000000000..52d39cedd7 --- /dev/null +++ b/chain/core/src/std/bech32_address/bech32_address_error.rs @@ -0,0 +1,26 @@ +/// Error type returned by [`super::Bech32Address::try_from_bech32_string`]. +#[derive(Debug)] +pub enum Bech32AddressError { + /// The string could not be decoded as a valid bech32 value. + /// Contains a message with the offending input and the underlying error. + DecodeError(String), + /// The decoded payload is not exactly 32 bytes. + /// Contains the actual decoded length. + InvalidLength(usize), +} + +impl core::fmt::Display for Bech32AddressError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Bech32AddressError::DecodeError(msg) => write!(f, "bech32 decode error: {msg}"), + Bech32AddressError::InvalidLength(len) => { + write!( + f, + "invalid address length after decoding: expected 32, got {len}" + ) + } + } + } +} + +impl std::error::Error for Bech32AddressError {} diff --git a/chain/core/src/std/bech32_address/bech32_hrp.rs b/chain/core/src/std/bech32_address/bech32_hrp.rs new file mode 100644 index 0000000000..6d89794d1a --- /dev/null +++ b/chain/core/src/std/bech32_address/bech32_hrp.rs @@ -0,0 +1,197 @@ +use bech32::Hrp; +use serde::{Deserialize, Serialize}; + +/// The default human-readable part (HRP) for MultiversX mainnet addresses: `"erd"`. +/// +/// Used as the [`Default`] value for [`Bech32Hrp`] and as the default HRP when +/// encoding addresses without an explicit HRP. +pub const ERD_HRP: Bech32Hrp = Bech32Hrp::parse_unchecked("erd"); + +/// Newtype wrapping the human-readable part (HRP) of a bech32 address. +/// +/// Wraps [`bech32::Hrp`], which validates and stores the HRP in a fixed-size buffer. +/// Because [`Hrp`] is [`Copy`], so is `Bech32Hrp`. +/// +/// Constants can be created with [`Bech32Hrp::parse_unchecked`], which is `const`: +/// +/// ```rust +/// # use multiversx_chain_core::std::Bech32Hrp; +/// const ERD_HRP: Bech32Hrp = Bech32Hrp::parse_unchecked("erd"); +/// ``` +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Bech32Hrp(Hrp); + +impl Bech32Hrp { + /// Creates a `Bech32Hrp` from a string slice without validation. + /// + /// This is a `const fn`, making it usable in constant and static contexts: + /// ```rust + /// # use multiversx_chain_core::std::Bech32Hrp; + /// const ERD: Bech32Hrp = Bech32Hrp::parse_unchecked("erd"); + /// ``` + /// + /// For runtime strings with validation use [`Bech32Hrp::from_string`]. + pub const fn parse_unchecked(hrp: &str) -> Self { + Bech32Hrp(Hrp::parse_unchecked(hrp)) + } + + /// Parses and creates a `Bech32Hrp` from an owned [`String`]. + /// + /// # Panics + /// + /// Panics if `hrp` is not a valid bech32 human-readable part. + pub fn from_string(hrp: String) -> Self { + Bech32Hrp(Hrp::parse(&hrp).unwrap_or_else(|err| panic!("invalid HRP '{hrp}': {err}"))) + } + + /// Returns the HRP as a borrowed `&str`. + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn to_inner(&self) -> Hrp { + self.0 + } +} + +impl Default for Bech32Hrp { + fn default() -> Self { + ERD_HRP + } +} + +impl From for Bech32Hrp { + fn from(hrp: Hrp) -> Self { + Bech32Hrp(hrp) + } +} + +impl TryFrom for Bech32Hrp { + type Error = bech32::primitives::hrp::Error; + + fn try_from(s: String) -> Result { + Hrp::parse(&s).map(Bech32Hrp) + } +} + +impl TryFrom<&str> for Bech32Hrp { + type Error = bech32::primitives::hrp::Error; + + fn try_from(s: &str) -> Result { + Hrp::parse(s).map(Bech32Hrp) + } +} + +impl PartialEq for Bech32Hrp { + fn eq(&self, other: &str) -> bool { + self.0.as_str() == other + } +} + +impl PartialEq<&str> for Bech32Hrp { + fn eq(&self, other: &&str) -> bool { + self.0.as_str() == *other + } +} + +impl core::fmt::Display for Bech32Hrp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl core::fmt::Debug for Bech32Hrp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("Bech32Hrp").field(&self.0.as_str()).finish() + } +} + +impl Serialize for Bech32Hrp { + fn serialize(&self, serializer: S) -> Result { + self.as_str().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Bech32Hrp { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Hrp::parse(&s) + .map(Bech32Hrp) + .map_err(|e| serde::de::Error::custom(format!("invalid HRP: {e}"))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_try_from_str_valid() { + let hrp = Bech32Hrp::try_from("erd").unwrap(); + assert_eq!(hrp.as_str(), "erd"); + + let hrp = Bech32Hrp::try_from("test").unwrap(); + assert_eq!(hrp.as_str(), "test"); + } + + #[test] + fn test_try_from_string_valid() { + let hrp = Bech32Hrp::try_from(String::from("erd")).unwrap(); + assert_eq!(hrp.as_str(), "erd"); + } + + #[test] + fn test_try_from_str_invalid() { + // HRP cannot be empty + assert!(Bech32Hrp::try_from("").is_err()); + // HRP cannot contain non-ASCII characters + assert!(Bech32Hrp::try_from("\u{e9}rd").is_err()); + } + + #[test] + fn test_try_from_string_invalid() { + assert!(Bech32Hrp::try_from(String::from("")).is_err()); + assert!(Bech32Hrp::try_from(String::from("\u{e9}rd")).is_err()); + } + + #[test] + fn test_parse_unchecked() { + let hrp = Bech32Hrp::parse_unchecked("erd"); + assert_eq!(hrp.as_str(), "erd"); + } + + #[test] + fn test_default_is_erd() { + assert_eq!(Bech32Hrp::default().as_str(), "erd"); + } + + #[test] + fn test_partial_eq_str() { + let hrp = Bech32Hrp::try_from("erd").unwrap(); + assert!(hrp == *"erd"); + assert!(hrp == "erd"); + assert!(hrp != *"test"); + assert!(hrp != "test"); + } + + #[test] + fn test_display() { + let hrp = Bech32Hrp::try_from("erd").unwrap(); + assert_eq!(hrp.to_string(), "erd"); + } + + #[test] + fn test_serde_roundtrip() { + let hrp = Bech32Hrp::try_from("erd").unwrap(); + let json = serde_json::to_string(&hrp).unwrap(); + assert_eq!(json, "\"erd\""); + let decoded: Bech32Hrp = serde_json::from_str(&json).unwrap(); + assert_eq!(decoded, hrp); + } + + #[test] + fn test_serde_deserialize_invalid() { + // Non-ASCII characters are not valid in an HRP + assert!(serde_json::from_str::("\"\u{e9}rd\"").is_err()); + } +} diff --git a/chain/core/src/std/crypto.rs b/chain/core/src/std/crypto.rs index e7f2cab9a7..23abf5fad6 100644 --- a/chain/core/src/std/crypto.rs +++ b/chain/core/src/std/crypto.rs @@ -1,22 +1,8 @@ //! Standard crypto functions and constants used across many MultiversX crates. -//! -//! TODO: move here the rest of the functions and the Wallet, with signing. -pub const SHA256_RESULT_LEN: usize = 32; -pub const KECCAK256_RESULT_LEN: usize = 32; +pub mod ed25519; +mod keccak256; +mod sha256; -/// Computes the SHA-256 hash of the given data. -/// -/// Returns a 32-byte array containing the hash digest. -pub fn sha256(data: &[u8]) -> [u8; SHA256_RESULT_LEN] { - use sha2::Digest; - sha2::Sha256::digest(data).into() -} - -/// Computes the Keccak-256 hash of the given data. -/// -/// Returns a 32-byte array containing the hash digest. -pub fn keccak256(data: &[u8]) -> [u8; KECCAK256_RESULT_LEN] { - use sha3::Digest; - sha3::Keccak256::digest(data).into() -} +pub use keccak256::{KECCAK256_RESULT_LEN, keccak256}; +pub use sha256::{SHA256_RESULT_LEN, sha256}; diff --git a/chain/core/src/std/crypto/ed25519.rs b/chain/core/src/std/crypto/ed25519.rs new file mode 100644 index 0000000000..e0aea57f3f --- /dev/null +++ b/chain/core/src/std/crypto/ed25519.rs @@ -0,0 +1,169 @@ +use ed25519_dalek::{Signer, Verifier}; + +/// An Ed25519 signing (private) key, wrapping a 32-byte seed. +#[derive(Clone, PartialEq, Eq)] +pub struct Ed25519SigningKey(ed25519_dalek::SigningKey); + +/// An Ed25519 verifying (public) key. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Ed25519VerifyingKey(ed25519_dalek::VerifyingKey); + +/// An Ed25519 signature. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Ed25519Signature(ed25519_dalek::Signature); + +impl Ed25519SigningKey { + /// Constructs a signing key from a 32-byte seed. + pub fn from_seed(seed: &[u8; 32]) -> Self { + Self(ed25519_dalek::SigningKey::from_bytes(seed)) + } + + /// Constructs a signing key from a 64-byte keypair (seed || public key). + /// Returns an error if the public key half does not match the seed half. + pub fn from_keypair_bytes(bytes: &[u8; 64]) -> Result { + ed25519_dalek::SigningKey::from_keypair_bytes(bytes).map(Self) + } + + /// Returns the 32-byte seed (secret scalar). + pub fn to_seed_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() + } + + /// Returns the full 64-byte keypair encoding (seed || public key). + pub fn to_keypair_bytes(&self) -> [u8; 64] { + self.0.to_keypair_bytes() + } + + /// Derives the corresponding verifying (public) key. + pub fn verifying_key(&self) -> Ed25519VerifyingKey { + Ed25519VerifyingKey(self.0.verifying_key()) + } + + /// Signs a message with this key. + pub fn sign(&self, message: &[u8]) -> Ed25519Signature { + Ed25519Signature(self.0.sign(message)) + } +} + +impl Ed25519VerifyingKey { + /// Constructs a verifying key from its 32-byte compressed representation. + /// Returns `None` if the bytes do not represent a valid curve point. + pub fn from_bytes(bytes: &[u8; 32]) -> Option { + ed25519_dalek::VerifyingKey::from_bytes(bytes) + .ok() + .map(Self) + } + + /// Returns the 32-byte compressed encoding of the public key. + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// Returns a reference to the 32-byte compressed encoding of the public key. + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() + } + + /// Verifies an Ed25519 signature. Returns `true` if the signature is valid. + pub fn verify(&self, message: &[u8], signature: &Ed25519Signature) -> bool { + self.0.verify(message, &signature.0).is_ok() + } +} + +impl Ed25519Signature { + /// Constructs a signature from its 64-byte representation. + pub fn from_bytes(bytes: &[u8; 64]) -> Self { + Self(ed25519_dalek::Signature::from_bytes(bytes)) + } + + /// Returns the 64-byte encoding of this signature. + pub fn to_bytes(&self) -> [u8; 64] { + self.0.to_bytes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Known-good vectors generated with ed25519-dalek 2.2.0 and verified via + // the roundtrip test below. The seed is arbitrary; the pk and sig are + // the values the library produces for (seed, empty message). + const SEED: [u8; 32] = + hex_literal::hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae3d55"); + const PK_BYTES: [u8; 32] = + hex_literal::hex!("700e2ce7c4b674427eab27ba820bcf6f0faebe68e09fe8564292114e41dc6a41"); + const SIG_BYTES: [u8; 64] = hex_literal::hex!( + "37b4bd5f28b61f55dc9673ae2895baceb863d9cf51780d040f98ad8cdc896cf5\ + be46be655a863525da0959f7f373611585e437e28ec971b7bd206ff9bd26e803" + ); + + #[test] + fn verifying_key_derived_from_seed() { + assert_eq!( + Ed25519SigningKey::from_seed(&SEED) + .verifying_key() + .to_bytes(), + PK_BYTES + ); + } + + #[test] + fn signing_key_from_seed_roundtrip() { + let sk = Ed25519SigningKey::from_seed(&SEED); + assert_eq!(sk.verifying_key().to_bytes(), PK_BYTES); + } + + #[test] + fn sign_produces_known_signature() { + let sk = Ed25519SigningKey::from_seed(&SEED); + assert_eq!(sk.sign(b"").to_bytes(), SIG_BYTES); + } + + #[test] + fn verify_valid_signature() { + let vk = Ed25519SigningKey::from_seed(&SEED).verifying_key(); + let sig = Ed25519Signature::from_bytes(&SIG_BYTES); + assert!(vk.verify(b"", &sig)); + } + + #[test] + fn verify_rejects_wrong_message() { + let vk = Ed25519SigningKey::from_seed(&SEED).verifying_key(); + let sig = Ed25519Signature::from_bytes(&SIG_BYTES); + assert!(!vk.verify(b"wrong", &sig)); + } + + #[test] + fn verify_rejects_wrong_signature() { + let vk = Ed25519SigningKey::from_seed(&SEED).verifying_key(); + let mut bad_bytes = SIG_BYTES; + bad_bytes[0] ^= 0xff; + let bad_sig = Ed25519Signature::from_bytes(&bad_bytes); + assert!(!vk.verify(b"", &bad_sig)); + } + + #[test] + fn verifying_key_from_bytes_roundtrip() { + let vk = Ed25519VerifyingKey::from_bytes(&PK_BYTES).unwrap(); + assert_eq!(vk.to_bytes(), PK_BYTES); + } + + #[test] + fn verifying_key_from_bytes_different_seeds_differ() { + let vk1 = Ed25519VerifyingKey::from_bytes(&PK_BYTES).unwrap(); + let vk2 = Ed25519SigningKey::from_seed(&[0x42u8; 32]).verifying_key(); + assert_ne!(vk1.to_bytes(), vk2.to_bytes()); + } + + #[test] + fn sign_then_verify_roundtrip() { + let seed: [u8; 32] = [0x42u8; 32]; + let sk = Ed25519SigningKey::from_seed(&seed); + let vk = sk.verifying_key(); + let msg = b"hello multiversx"; + let sig = sk.sign(msg); + assert!(vk.verify(msg, &sig)); + assert!(!vk.verify(b"other message", &sig)); + } +} diff --git a/chain/core/src/std/crypto/keccak256.rs b/chain/core/src/std/crypto/keccak256.rs new file mode 100644 index 0000000000..619bf4d80c --- /dev/null +++ b/chain/core/src/std/crypto/keccak256.rs @@ -0,0 +1,36 @@ +pub const KECCAK256_RESULT_LEN: usize = 32; + +/// Computes the Keccak-256 hash of the given data. +/// +/// Returns a 32-byte array containing the hash digest. +pub fn keccak256(data: &[u8]) -> [u8; KECCAK256_RESULT_LEN] { + use sha3::Digest; + sha3::Keccak256::digest(data).into() +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn keccak256_empty() { + // Keccak-256("") = + // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 + let expected = hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + assert_eq!(keccak256(b""), expected); + } + + #[test] + fn keccak256_abc() { + // Keccak-256("abc") = + // 4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 + let expected = hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"); + assert_eq!(keccak256(b"abc"), expected); + } + + #[test] + fn keccak256_different_inputs_differ() { + assert_ne!(keccak256(b"foo"), keccak256(b"bar")); + } +} diff --git a/chain/core/src/std/crypto/sha256.rs b/chain/core/src/std/crypto/sha256.rs new file mode 100644 index 0000000000..1d19d963d0 --- /dev/null +++ b/chain/core/src/std/crypto/sha256.rs @@ -0,0 +1,36 @@ +pub const SHA256_RESULT_LEN: usize = 32; + +/// Computes the SHA-256 hash of the given data. +/// +/// Returns a 32-byte array containing the hash digest. +pub fn sha256(data: &[u8]) -> [u8; SHA256_RESULT_LEN] { + use sha2::Digest; + sha2::Sha256::digest(data).into() +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn sha256_empty() { + // NIST FIPS 180-4: SHA-256("") = + // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + let expected = hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert_eq!(sha256(b""), expected); + } + + #[test] + fn sha256_abc() { + // NIST FIPS 180-4: SHA-256("abc") = + // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad + let expected = hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + assert_eq!(sha256(b"abc"), expected); + } + + #[test] + fn sha256_different_inputs_differ() { + assert_ne!(sha256(b"foo"), sha256(b"bar")); + } +} diff --git a/chain/core/src/std/new_address.rs b/chain/core/src/std/new_address.rs index 7b1ff3a50f..bdbd49482b 100644 --- a/chain/core/src/std/new_address.rs +++ b/chain/core/src/std/new_address.rs @@ -58,7 +58,7 @@ pub fn compute_new_address_bech32( deployment_nonce: u64, ) -> Bech32Address { let address = compute_new_address(&deployer.address, deployment_nonce); - Bech32Address::encode_address(&deployer.hrp, address) + Bech32Address::encode_address(deployer.hrp, address) } /// Generates a mock smart contract address deterministically from a creator address and nonce. diff --git a/chain/core/src/token_identifier_util.rs b/chain/core/src/token_identifier_util.rs index 55c424fbcd..5c9d412a6c 100644 --- a/chain/core/src/token_identifier_util.rs +++ b/chain/core/src/token_identifier_util.rs @@ -1,3 +1,16 @@ +/// Error type returned by token identifier validation functions. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenIdValidationError { + /// Token identifier length is outside the allowed range. + InvalidLength, + /// Ticker contains a character that is not uppercase alphanumeric. + InvalidTickerChar, + /// The dash separator is missing or not at the expected position. + MissingOrMisplacedDash, + /// The random suffix contains a character outside `[0-9a-f]`. + InvalidRandomChar, +} + const TICKER_MIN_LENGTH: usize = 3; const TICKER_MAX_LENGTH: usize = 10; const ADDITIONAL_RANDOM_CHARS_LENGTH: usize = 6; @@ -8,52 +21,170 @@ pub const IDENTIFIER_MAX_LENGTH: usize = TICKER_MAX_LENGTH + ADDITIONAL_RANDOM_C const DASH_CHARACTER: u8 = b'-'; -/// There is a VM implementation of this very function. -/// This implementation is used for debugging mode and backwards compatibility. -/// Using the VM implementation instead of this one saves ~0.6 kB of contract code. -pub fn validate_token_identifier(token_id_slice: &[u8]) -> bool { - let length = token_id_slice.len(); - - #[allow(clippy::manual_range_contains)] - if length < IDENTIFIER_MIN_LENGTH || length > IDENTIFIER_MAX_LENGTH { - return false; - } - - let lowercase_letter_range = b'a'..=b'z'; +/// Validates the ticker portion of a token identifier (uppercase alphanumeric). +/// +/// Expects `ticker` to be the slice before the dash. +fn validate_ticker(ticker: &[u8]) -> Result<(), TokenIdValidationError> { let uppercase_letter_range = b'A'..=b'Z'; let number_range = b'0'..=b'9'; - // ticker must be all uppercase alphanumeric - let ticker_len = get_token_ticker_len(length); - let ticker = &token_id_slice[..ticker_len]; for ticker_char in ticker { let is_uppercase_letter = uppercase_letter_range.contains(ticker_char); let is_number = number_range.contains(ticker_char); if !is_uppercase_letter && !is_number { - return false; + return Err(TokenIdValidationError::InvalidTickerChar); } } - let dash_position = ticker_len; - if token_id_slice[dash_position] != DASH_CHARACTER { - return false; - } + Ok(()) +} + +/// Validates the random suffix of a token identifier (lowercase hex digits `[0-9a-f]`). +/// +/// The suffix is hex-encoded from 3 bytes, so only `[0-9a-f]{6}` is valid. +fn validate_random_chars(random_chars: &[u8]) -> Result<(), TokenIdValidationError> { + let hex_letter_range = b'a'..=b'f'; + let number_range = b'0'..=b'9'; - // random chars are alphanumeric lowercase - let random_chars = &token_id_slice[(length - ADDITIONAL_RANDOM_CHARS_LENGTH)..]; for rand_char in random_chars { - let is_lowercase_letter = lowercase_letter_range.contains(rand_char); + let is_hex_letter = hex_letter_range.contains(rand_char); let is_number = number_range.contains(rand_char); - if !is_lowercase_letter && !is_number { - return false; + if !is_hex_letter && !is_number { + return Err(TokenIdValidationError::InvalidRandomChar); } } - true + Ok(()) +} + +/// There is a VM implementation of this very function. +/// This implementation is used for debugging mode and backwards compatibility. +/// Using the VM implementation instead of this one saves ~0.6 kB of contract code. +pub fn validate_token_identifier(token_id_slice: &[u8]) -> Result<(), TokenIdValidationError> { + let length = token_id_slice.len(); + + #[allow(clippy::manual_range_contains)] + if length < IDENTIFIER_MIN_LENGTH || length > IDENTIFIER_MAX_LENGTH { + return Err(TokenIdValidationError::InvalidLength); + } + + // The dash must be at the fixed position separating the ticker from the 6-char suffix. + let dash_pos = get_token_ticker_len(length); + if token_id_slice[dash_pos] != DASH_CHARACTER { + return Err(TokenIdValidationError::MissingOrMisplacedDash); + } + + validate_ticker(&token_id_slice[..dash_pos])?; + + validate_random_chars(&token_id_slice[(dash_pos + 1)..]) } pub fn get_token_ticker_len(token_id_len: usize) -> usize { token_id_len - ADDITIONAL_RANDOM_CHARS_LENGTH - 1 } + +#[cfg(test)] +mod tests { + use super::{TokenIdValidationError, validate_token_identifier}; + + fn valid(token_id: &str) { + assert_eq!(validate_token_identifier(token_id.as_bytes()), Ok(())); + } + + fn invalid(token_id: &str, expected_err: TokenIdValidationError) { + assert_eq!( + validate_token_identifier(token_id.as_bytes()), + Err(expected_err) + ); + } + + #[test] + fn test_valid_identifiers() { + valid("ALC-6258d2"); + valid("ALC123-6258d2"); + valid("12345-6258d2"); + // max-length ticker (10 chars) + valid("EGLDRIDEFL-08d8ef"); + // number at end of max-length ticker + valid("EGLDRIDEF2-08d8ef"); + } + + #[test] + fn test_invalid_length() { + // total too short: ticker 0 chars + invalid("-6258d2", TokenIdValidationError::InvalidLength); + // total too short: ticker 2 chars + invalid("AL-6258d2", TokenIdValidationError::InvalidLength); + // total too short: no dash, 9 chars + invalid("ALC6258d2", TokenIdValidationError::InvalidLength); + // total too long: ticker 11 chars + invalid("ALCCCCCCCCC-6258d2", TokenIdValidationError::InvalidLength); + // total too long: suffix 7 chars + invalid("EGLDRIDEFL-08d8eff", TokenIdValidationError::InvalidLength); + } + + #[test] + fn test_missing_or_misplaced_dash() { + // no dash at expected position (valid length, but no dash at pos 3) + invalid("ABCX6258d2", TokenIdValidationError::MissingOrMisplacedDash); + // dash too early (valid length 10, dash at pos 2, expected at pos 3) + invalid("AL-C6258d2", TokenIdValidationError::MissingOrMisplacedDash); + // dash too early (valid length 12, dash at pos 3, expected at pos 5) + invalid( + "ALC-6258d2ff", + TokenIdValidationError::MissingOrMisplacedDash, + ); + // dash too late (valid length 16, dash at pos 10, expected at pos 9) + invalid( + "EGLDRIDEFL-08d8e", + TokenIdValidationError::MissingOrMisplacedDash, + ); + } + + #[test] + fn test_invalid_ticker_char() { + // all lowercase ticker + invalid("alc-6258d2", TokenIdValidationError::InvalidTickerChar); + // lowercase char in ticker + invalid( + "EGLDRIDEFl-08d8ef", + TokenIdValidationError::InvalidTickerChar, + ); + // special char in ticker + invalid( + "EGLDRIDEF*-08d8ef", + TokenIdValidationError::InvalidTickerChar, + ); + // extra dash inside the ticker (dash at expected pos, but ticker contains a dash) + // "AL-C-6258d2" (11 chars): dash_pos=4, ticker="AL-C" → InvalidTickerChar + invalid("AL-C-6258d2", TokenIdValidationError::InvalidTickerChar); + // "ALC--6258d2" (11 chars): dash_pos=4, ticker="ALC-" → InvalidTickerChar + invalid("ALC--6258d2", TokenIdValidationError::InvalidTickerChar); + // identifier starting with a dash: dash_pos=3, ticker="-LC" → InvalidTickerChar + invalid("-LC-6258d2", TokenIdValidationError::InvalidTickerChar); + } + + #[test] + fn test_invalid_random_char() { + // uppercase char in random part + invalid("ALC-6258D2", TokenIdValidationError::InvalidRandomChar); + invalid( + "EGLDRIDEFL-08d8eF", + TokenIdValidationError::InvalidRandomChar, + ); + // special char in random part + invalid( + "EGLDRIDEFL-08d*ef", + TokenIdValidationError::InvalidRandomChar, + ); + // non-hex lowercase letters (g-z) in random part + invalid("ABC-ghijkl", TokenIdValidationError::InvalidRandomChar); + invalid("ALC-6258g2", TokenIdValidationError::InvalidRandomChar); + invalid("ALC-zzzzzz", TokenIdValidationError::InvalidRandomChar); + // dash in the random part (dash at expected pos, suffix contains a dash) + // "ALC-625-d2" (10 chars): dash_pos=3, ticker="ALC" ✓, suffix="625-d2" → InvalidRandomChar + invalid("ALC-625-d2", TokenIdValidationError::InvalidRandomChar); + } +} diff --git a/chain/core/src/types/address.rs b/chain/core/src/types/address.rs index 515a1d757a..407c0c3ab7 100644 --- a/chain/core/src/types/address.rs +++ b/chain/core/src/types/address.rs @@ -48,7 +48,7 @@ impl Address { /// Encodes the address as a bech32 string using the given human-readable part (HRP). #[cfg(feature = "std")] - pub fn to_bech32(&self, hrp: &str) -> crate::std::Bech32Address { + pub fn to_bech32(&self, hrp: crate::std::Bech32Hrp) -> crate::std::Bech32Address { crate::std::Bech32Address::encode_address(hrp, self.clone()) } diff --git a/chain/core/src/types/flags.rs b/chain/core/src/types/flags.rs index 6b2522c386..9d2db6dc65 100644 --- a/chain/core/src/types/flags.rs +++ b/chain/core/src/types/flags.rs @@ -5,7 +5,7 @@ mod esdt_token_type; mod return_code; mod token_type; -pub use code_metadata::CodeMetadata; +pub use code_metadata::{CodeMetadata, CodeMetadataError}; pub use esdt_local_role::EsdtLocalRole; pub use esdt_local_role_flags::EsdtLocalRoleFlags; pub use esdt_token_type::EsdtTokenType; diff --git a/chain/core/src/types/flags/code_metadata.rs b/chain/core/src/types/flags/code_metadata.rs index ee55b3c734..e190c8d33b 100644 --- a/chain/core/src/types/flags/code_metadata.rs +++ b/chain/core/src/types/flags/code_metadata.rs @@ -5,16 +5,48 @@ use bitflags::bitflags; const UPGRADEABLE_STRING: &str = "Upgradeable"; const READABLE_STRING: &str = "Readable"; +const GUARDED_STRING: &str = "Guarded"; const PAYABLE_STRING: &str = "Payable"; const PAYABLE_BY_SC_STRING: &str = "PayableBySC"; const DEFAULT_STRING: &str = "Default"; bitflags! { - /// Flags representing the smart contract's allowed actions after deploy. - #[derive(Default, PartialEq, Debug, Clone, Copy)] + /// Bit-flags that govern a smart contract's permissions after deployment. + /// + /// Each flag corresponds to a protocol-level capability: + /// - [`UPGRADEABLE`](CodeMetadata::UPGRADEABLE) — the contract may be upgraded by its owner. + /// - [`READABLE`](CodeMetadata::READABLE) — other contracts may read this contract's storage. + /// - [`GUARDED`](CodeMetadata::GUARDED) — transactions require a second guardian signature. + /// - [`PAYABLE`](CodeMetadata::PAYABLE) — the contract accepts direct EGLD/ESDT transfers. + /// - [`PAYABLE_BY_SC`](CodeMetadata::PAYABLE_BY_SC) — the contract accepts transfers from other contracts only. + /// + /// # Wire format + /// + /// Metadata is serialised as exactly **2 big-endian bytes**. The first byte carries + /// `UPGRADEABLE` (bit 0), `READABLE` (bit 2), and `GUARDED` (bit 3); the second byte carries + /// `PAYABLE` (bit 1) and `PAYABLE_BY_SC` (bit 2). + /// + /// # Parsing + /// + /// - Use [`TryFrom<&[u8]>`] / [`TryFrom`] in **tooling and off-chain code** — these + /// return [`CodeMetadataError`] on wrong-length input or unknown bits, enabling fail-closed + /// validation. + /// - Use [`from_bytes_or_default`](CodeMetadata::from_bytes_or_default) in the **Rust VM** — + /// this mirrors the lenient Go protocol implementation: wrong-length input yields + /// [`EMPTY`](CodeMetadata::EMPTY) and unknown bits are silently truncated. + /// + /// # Default value + /// + /// [`Default::default()`] returns `UPGRADEABLE | READABLE | PAYABLE | PAYABLE_BY_SC`, + /// which is the permissive set used as a fallback in **scenario tests** when a deployed + /// contract's metadata field is left unset. This is intentionally *not* the zero value; + /// use [`EMPTY`](CodeMetadata::EMPTY) explicitly when you need all flags cleared. + #[derive(PartialEq, Debug, Clone, Copy)] pub struct CodeMetadata: u16 { - /// No flags set. The contract is not upgradeable, not readable, and not payable. - const DEFAULT = 0; + /// No flags set. The contract is not upgradeable, not readable, and not payable, has no guardian. + /// + /// Warning! This is not the default value, see CodeMetadata::default() for that. + const EMPTY = 0; /// The contract can be upgraded in the future. const UPGRADEABLE = 0b0000_0001_0000_0000; // LSB of first byte @@ -22,6 +54,10 @@ bitflags! { /// The contract's storage can be read by other contracts. const READABLE = 0b0000_0100_0000_0000; // 3rd LSB of first byte + /// The contract is guarded: transactions must carry a second signature + /// from a guardian account in order to be executed. + const GUARDED = 0b0000_1000_0000_0000; // 4th LSB of first byte + /// The contract can receive funds without having any endpoint called, /// just like user accounts. /// @@ -35,95 +71,167 @@ bitflags! { } } +impl Default for CodeMetadata { + fn default() -> Self { + CodeMetadata::UPGRADEABLE + | CodeMetadata::READABLE + | CodeMetadata::PAYABLE + | CodeMetadata::PAYABLE_BY_SC + } +} + impl CodeMetadata { /// Returns `true` if the contract is allowed to be upgraded. pub fn is_upgradeable(&self) -> bool { - *self & CodeMetadata::UPGRADEABLE != CodeMetadata::DEFAULT + *self & CodeMetadata::UPGRADEABLE != CodeMetadata::EMPTY } /// Returns `true` if the contract can receive funds without an endpoint call. pub fn is_payable(&self) -> bool { - *self & CodeMetadata::PAYABLE != CodeMetadata::DEFAULT + *self & CodeMetadata::PAYABLE != CodeMetadata::EMPTY } /// Returns `true` if the contract can receive funds from other smart contracts /// without an endpoint call. Direct user transfers are rejected. pub fn is_payable_by_sc(&self) -> bool { - *self & CodeMetadata::PAYABLE_BY_SC != CodeMetadata::DEFAULT + *self & CodeMetadata::PAYABLE_BY_SC != CodeMetadata::EMPTY } /// Returns `true` if the contract's storage can be read by other contracts. pub fn is_readable(&self) -> bool { - *self & CodeMetadata::READABLE != CodeMetadata::DEFAULT + *self & CodeMetadata::READABLE != CodeMetadata::EMPTY + } + + /// Returns `true` if the contract is guarded. + pub fn is_guarded(&self) -> bool { + *self & CodeMetadata::GUARDED != CodeMetadata::EMPTY } + /// Serialises the metadata to its 2-byte big-endian wire representation. + /// + /// The returned array is the canonical on-chain encoding: first byte carries + /// `UPGRADEABLE`, `READABLE`, and `GUARDED`; second byte carries `PAYABLE` and `PAYABLE_BY_SC`. #[inline] pub fn to_byte_array(&self) -> [u8; 2] { self.bits().to_be_bytes() } + /// Serialises the metadata to a 2-byte big-endian `Vec`. + /// + /// Convenience wrapper around [`to_byte_array`](Self::to_byte_array) for call sites + /// that need an owned buffer. pub fn to_vec(&self) -> Vec { self.to_byte_array().to_vec() } - pub fn for_each_string_token(&self, mut f: F) { + /// Calls `f` once for each token that makes up the human-readable display of these flags. + /// + /// Tokens are the flag name strings (`"Upgradeable"`, `"Readable"`, `"Guarded"`, + /// `"Payable"`, `"PayableBySC"`) and the `"|"` separator between them. + /// If no flags are set, `f` is called once with `"Default"`. + /// + /// The flag order matches the canonical display order used in scenario JSON files. + pub fn write_display_tokens(&self, mut write: F) { let mut nothing_printed: bool = true; if self.is_upgradeable() { - f(UPGRADEABLE_STRING); + write(UPGRADEABLE_STRING); nothing_printed = false; } if self.is_readable() { if !nothing_printed { - f("|"); + write("|"); + } + write(READABLE_STRING); + nothing_printed = false; + } + if self.is_guarded() { + if !nothing_printed { + write("|"); } - f(READABLE_STRING); + write(GUARDED_STRING); nothing_printed = false; } if self.is_payable() { if !nothing_printed { - f("|"); + write("|"); } - f(PAYABLE_STRING); + write(PAYABLE_STRING); nothing_printed = false; } if self.is_payable_by_sc() { if !nothing_printed { - f("|"); + write("|"); } - f(PAYABLE_BY_SC_STRING); + write(PAYABLE_BY_SC_STRING); nothing_printed = false; } if nothing_printed { - f(DEFAULT_STRING); + write(DEFAULT_STRING); + } + } + + /// Parses code metadata from a 2-byte slice, mirroring the MultiversX protocol implementation. + /// + /// If the slice is not exactly 2 bytes, returns [`CodeMetadata::EMPTY`] (all flags cleared). + /// Unknown or reserved bits are silently ignored; only the bits corresponding to known flags + /// are extracted. + /// + /// This intentionally lenient behaviour matches the on-chain Go implementation + /// (`CodeMetadataFromBytes`). Prefer [`TryFrom<&[u8]>`] in tooling and off-chain code + /// where strict validation is desirable. + pub fn from_bytes_or_default(bytes: &[u8]) -> Self { + if bytes.len() != 2 { + return CodeMetadata::EMPTY; } + let value = u16::from_be_bytes([bytes[0], bytes[1]]); + CodeMetadata::from_bits_truncate(value) } } -impl From<[u8; 2]> for CodeMetadata { +/// Error type returned when converting raw bytes or integers into [`CodeMetadata`] fails. +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum CodeMetadataError { + /// The input slice was not exactly 2 bytes long. + InvalidLength, + /// The bit pattern contains bits that do not correspond to any known flag. + InvalidBits(u16), +} + +impl TryFrom for CodeMetadata { + type Error = CodeMetadataError; + #[inline] - fn from(arr: [u8; 2]) -> Self { - CodeMetadata::from(u16::from_be_bytes(arr)) + fn try_from(value: u16) -> Result { + CodeMetadata::from_bits(value).ok_or(CodeMetadataError::InvalidBits(value)) } } -impl From for CodeMetadata { +impl TryFrom<[u8; 2]> for CodeMetadata { + type Error = CodeMetadataError; + #[inline] - fn from(value: u16) -> Self { - CodeMetadata::from_bits_truncate(value) + fn try_from(arr: [u8; 2]) -> Result { + CodeMetadata::try_from(u16::from_be_bytes(arr)) } } -impl From<&[u8]> for CodeMetadata { - fn from(slice: &[u8]) -> Self { - let arr: [u8; 2] = slice.try_into().unwrap_or_default(); - CodeMetadata::from(arr) +impl TryFrom<&[u8]> for CodeMetadata { + type Error = CodeMetadataError; + + fn try_from(slice: &[u8]) -> Result { + let arr: [u8; 2] = slice + .try_into() + .map_err(|_| CodeMetadataError::InvalidLength)?; + CodeMetadata::try_from(arr) } } -impl From<&Vec> for CodeMetadata { - fn from(v: &Vec) -> Self { - CodeMetadata::from(v.as_slice()) +impl TryFrom<&Vec> for CodeMetadata { + type Error = CodeMetadataError; + + fn try_from(v: &Vec) -> Result { + CodeMetadata::try_from(v.as_slice()) } } @@ -155,7 +263,8 @@ impl NestedDecode for CodeMetadata { I: NestedDecodeInput, H: DecodeErrorHandler, { - Ok(CodeMetadata::from(u16::dep_decode_or_handle_err(input, h)?)) + let value = u16::dep_decode_or_handle_err(input, h)?; + CodeMetadata::try_from(value).map_err(|_| h.handle_error(DecodeError::INVALID_VALUE)) } } @@ -174,10 +283,12 @@ mod tests { use super::*; #[test] - fn test_default() { - assert!(!CodeMetadata::DEFAULT.is_upgradeable()); - assert!(!CodeMetadata::DEFAULT.is_payable()); - assert!(!CodeMetadata::DEFAULT.is_readable()); + fn test_empty() { + assert!(!CodeMetadata::EMPTY.is_upgradeable()); + assert!(!CodeMetadata::EMPTY.is_payable()); + assert!(!CodeMetadata::EMPTY.is_payable_by_sc()); + assert!(!CodeMetadata::EMPTY.is_readable()); + assert!(!CodeMetadata::EMPTY.is_guarded()); } #[test] @@ -185,50 +296,102 @@ mod tests { let all = CodeMetadata::UPGRADEABLE | CodeMetadata::PAYABLE | CodeMetadata::PAYABLE_BY_SC - | CodeMetadata::READABLE; + | CodeMetadata::READABLE + | CodeMetadata::GUARDED; assert!(all.is_upgradeable()); assert!(all.is_payable()); assert!(all.is_payable_by_sc()); assert!(all.is_readable()); + assert!(all.is_guarded()); - assert_eq!(all.bits(), 0x0506); + assert_eq!(all.bits(), 0x0d06); assert_eq!(CodeMetadata::from_bits_truncate(0xffff), all); } #[test] fn test_each() { - assert!(CodeMetadata::UPGRADEABLE.is_upgradeable()); - assert!(!CodeMetadata::PAYABLE.is_upgradeable()); - assert!(!CodeMetadata::PAYABLE_BY_SC.is_upgradeable()); - assert!(!CodeMetadata::READABLE.is_upgradeable()); + type CodeMetadataChecker = fn(&CodeMetadata) -> bool; + let flags_and_checkers: &[(CodeMetadata, CodeMetadataChecker)] = &[ + (CodeMetadata::UPGRADEABLE, CodeMetadata::is_upgradeable), + (CodeMetadata::READABLE, CodeMetadata::is_readable), + (CodeMetadata::GUARDED, CodeMetadata::is_guarded), + (CodeMetadata::PAYABLE, CodeMetadata::is_payable), + (CodeMetadata::PAYABLE_BY_SC, CodeMetadata::is_payable_by_sc), + ]; + + // checking that checkers match, and are orthogonal (each checker only returns true for its own flag) + for (flag, checker) in flags_and_checkers { + for (other_flag, _) in flags_and_checkers { + assert_eq!( + checker(other_flag), + flag == other_flag, + "{checker:?} returned unexpected result for flag {other_flag:?}", + ); + } + } + } - assert!(!CodeMetadata::UPGRADEABLE.is_payable()); - assert!(CodeMetadata::PAYABLE.is_payable()); - assert!(!CodeMetadata::PAYABLE_BY_SC.is_payable()); - assert!(!CodeMetadata::READABLE.is_payable()); + #[test] + fn test_try_from_slice_exact_length() { + assert!( + CodeMetadata::try_from(&[1u8, 0u8][..]) + .unwrap() + .is_upgradeable() + ); + assert!( + CodeMetadata::try_from(&[0u8, 2u8][..]) + .unwrap() + .is_payable() + ); + assert!( + !CodeMetadata::try_from(&[0u8, 0u8][..]) + .unwrap() + .is_upgradeable() + ); + } - assert!(!CodeMetadata::UPGRADEABLE.is_payable_by_sc()); - assert!(!CodeMetadata::PAYABLE.is_payable_by_sc()); - assert!(CodeMetadata::PAYABLE_BY_SC.is_payable_by_sc()); - assert!(!CodeMetadata::READABLE.is_payable_by_sc()); + #[test] + fn test_try_from_slice_wrong_length() { + assert_eq!( + CodeMetadata::try_from(&[1u8][..]), + Err(CodeMetadataError::InvalidLength) + ); + assert_eq!( + CodeMetadata::try_from(&[][..]), + Err(CodeMetadataError::InvalidLength) + ); + assert_eq!( + CodeMetadata::try_from(&[1u8, 0u8, 0u8][..]), + Err(CodeMetadataError::InvalidLength) + ); + } - assert!(!CodeMetadata::UPGRADEABLE.is_readable()); - assert!(!CodeMetadata::PAYABLE.is_readable()); - assert!(!CodeMetadata::PAYABLE_BY_SC.is_readable()); - assert!(CodeMetadata::READABLE.is_readable()); + #[test] + fn test_try_from_invalid_bits() { + // 0x0001 has no defined flag in the second byte at bit 0 + assert_eq!( + CodeMetadata::try_from(0x0001u16), + Err(CodeMetadataError::InvalidBits(0x0001)) + ); } /// Translated from vm-wasm. #[test] - fn test_from_array() { - assert!(CodeMetadata::from([1, 0]).is_upgradeable()); - assert!(!CodeMetadata::from([1, 0]).is_readable()); - assert!(CodeMetadata::from([0, 2]).is_payable()); - assert!(CodeMetadata::from([4, 0]).is_readable()); - assert!(!CodeMetadata::from([4, 0]).is_upgradeable()); - assert!(!CodeMetadata::from([0, 0]).is_upgradeable()); - assert!(!CodeMetadata::from([0, 0]).is_payable()); - assert!(!CodeMetadata::from([0, 0]).is_readable()); + fn test_try_from_array() { + assert!(CodeMetadata::try_from([1u8, 0u8]).unwrap().is_upgradeable()); + assert!(!CodeMetadata::try_from([1u8, 0u8]).unwrap().is_readable()); + assert!(!CodeMetadata::try_from([1u8, 0u8]).unwrap().is_guarded()); + assert!(CodeMetadata::try_from([0u8, 2u8]).unwrap().is_payable()); + assert!(CodeMetadata::try_from([4u8, 0u8]).unwrap().is_readable()); + assert!(!CodeMetadata::try_from([4u8, 0u8]).unwrap().is_upgradeable()); + assert!(!CodeMetadata::try_from([4u8, 0u8]).unwrap().is_guarded()); + assert!(CodeMetadata::try_from([8u8, 0u8]).unwrap().is_guarded()); + assert!(!CodeMetadata::try_from([8u8, 0u8]).unwrap().is_upgradeable()); + assert!(!CodeMetadata::try_from([8u8, 0u8]).unwrap().is_readable()); + assert!(!CodeMetadata::try_from([0u8, 0u8]).unwrap().is_upgradeable()); + assert!(!CodeMetadata::try_from([0u8, 0u8]).unwrap().is_payable()); + assert!(!CodeMetadata::try_from([0u8, 0u8]).unwrap().is_readable()); + assert!(!CodeMetadata::try_from([0u8, 0u8]).unwrap().is_guarded()); } } diff --git a/chain/core/src/types/flags/esdt_local_role.rs b/chain/core/src/types/flags/esdt_local_role.rs index 4feedd654e..3a6e0cf819 100644 --- a/chain/core/src/types/flags/esdt_local_role.rs +++ b/chain/core/src/types/flags/esdt_local_role.rs @@ -18,6 +18,19 @@ const ESDT_ROLE_MODIFY_CREATOR: &str = "ESDTRoleModifyCreator"; const ESDT_ROLE_NFT_RECREATE: &str = "ESDTRoleNFTRecreate"; const ESDT_ROLE_TRANSFER: &str = "ESDTTransferRole"; +/// An ESDT local role that can be granted to or revoked from an account for a specific token. +/// +/// Each role has two canonical representations: +/// - A **byte-string name** (e.g. `"ESDTRoleLocalMint"`) used in on-chain role +/// assignment, decoded via `From<&[u8]>`. +/// - A **numeric ID** (`u16`) used for `ManagedVecItem` payload encoding, +/// accessed via [`as_u16`][EsdtLocalRole::as_u16] and decoded via `From`. +/// +/// The numeric IDs and bit-flag positions correspond to the `Role*` iota constants +/// in `vmhost/vmhooks/eei_helpers.go` of `mx-chain-vm-go`. +/// +/// The declaration order matches the Go VM `iota` ordering so that the codec +/// discriminant (declaration index) aligns with [`as_u16`][EsdtLocalRole::as_u16]. #[derive(TopDecode, TopEncode, NestedDecode, NestedEncode, Clone, PartialEq, Eq, Debug, Copy)] pub enum EsdtLocalRole { None, @@ -29,13 +42,20 @@ pub enum EsdtLocalRole { NftUpdateAttributes, NftAddUri, NftRecreate, - ModifyRoyalties, ModifyCreator, + ModifyRoyalties, SetNewUri, Transfer, } impl EsdtLocalRole { + /// Returns the 1-based ordinal role ID used for `ManagedVecItem` payload encoding. + /// + /// This is a sequential index (1..=12), **not** a bitflag value. The + /// corresponding bitflag in [`EsdtLocalRoleFlags`] is `1 << (id - 1)`, which + /// aligns with the Go VM's `Role*` iota ordering in + /// `vmhost/vmhooks/eei_helpers.go`. `Transfer` (12) has no counterpart in + /// the current Go VM iota. This is the inverse of `From`. pub fn as_u16(&self) -> u16 { match self { Self::None => 0, @@ -54,10 +74,17 @@ impl EsdtLocalRole { } } + /// Returns the canonical byte-string name of this role (e.g. `b"ESDTRoleLocalMint"`). + /// + /// This is the format used in on-chain ESDT role grant/revoke operations. pub fn as_role_name(&self) -> &'static [u8] { self.name().as_bytes() } + /// Returns the canonical string name of this role (e.g. `"ESDTRoleLocalMint"`). + /// + /// This is the format used in on-chain ESDT role grant/revoke operations and + /// corresponds to the role-name constants in the Go VM. pub fn name(&self) -> &'static str { match self { Self::None => ESDT_ROLE_NONE, @@ -76,6 +103,9 @@ impl EsdtLocalRole { } } + /// Converts this role to its corresponding bit-flag in [`EsdtLocalRoleFlags`]. + /// + /// The bit position matches the Go VM's `Role*` iota constant for the same role. pub fn to_flag(&self) -> EsdtLocalRoleFlags { match self { Self::None => EsdtLocalRoleFlags::NONE, @@ -106,19 +136,24 @@ const ALL_ROLES: [EsdtLocalRole; 12] = [ EsdtLocalRole::NftUpdateAttributes, EsdtLocalRole::NftAddUri, EsdtLocalRole::NftRecreate, - EsdtLocalRole::ModifyRoyalties, EsdtLocalRole::ModifyCreator, + EsdtLocalRole::ModifyRoyalties, EsdtLocalRole::SetNewUri, EsdtLocalRole::Transfer, ]; impl EsdtLocalRole { + /// Iterates over all non-`None` roles in canonical numeric-ID order. pub fn iter_all() -> core::slice::Iter<'static, EsdtLocalRole> { ALL_ROLES.iter() } } impl From for EsdtLocalRole { + /// Decodes a numeric role ID into an [`EsdtLocalRole`]. + /// + /// This is the inverse of [`EsdtLocalRole::as_u16`]. Unknown values map to + /// [`EsdtLocalRole::None`]. #[inline] fn from(value: u16) -> Self { match value { @@ -130,8 +165,8 @@ impl From for EsdtLocalRole { 6 => Self::NftUpdateAttributes, 7 => Self::NftAddUri, 8 => Self::NftRecreate, - 9 => Self::ModifyRoyalties, - 10 => Self::ModifyCreator, + 9 => Self::ModifyCreator, + 10 => Self::ModifyRoyalties, 11 => Self::SetNewUri, 12 => Self::Transfer, _ => Self::None, @@ -140,6 +175,11 @@ impl From for EsdtLocalRole { } impl<'a> From<&'a [u8]> for EsdtLocalRole { + /// Decodes an ESDT role from its canonical byte-string name + /// (e.g. `b"ESDTRoleLocalMint"`). + /// + /// This is the primary decoding path for on-chain ESDT role data. Unknown + /// byte strings map to [`EsdtLocalRole::None`]. #[inline] fn from(byte_slice: &'a [u8]) -> Self { if byte_slice == ESDT_ROLE_LOCAL_MINT.as_bytes() { diff --git a/chain/core/src/types/flags/esdt_local_role_flags.rs b/chain/core/src/types/flags/esdt_local_role_flags.rs index 11f8d5fcba..da1ef103aa 100644 --- a/chain/core/src/types/flags/esdt_local_role_flags.rs +++ b/chain/core/src/types/flags/esdt_local_role_flags.rs @@ -2,7 +2,29 @@ use super::EsdtLocalRole; use bitflags::bitflags; bitflags! { - #[derive(PartialEq, Clone, Copy)] + /// Bit-flag representation of ESDT local roles. + /// + /// Each flag corresponds to a `Role*` constant defined in the Go VM at + /// `vmhost/vmhooks/eei_helpers.go` (`mx-chain-vm-go`), where the same + /// values are produced via `1 << iota` starting from `RoleMint = 1`. + /// + /// Correspondence table (Rust flag → Go constant → bit): + /// + /// | Rust flag | Go constant | Bit | + /// |------------------------|--------------------------|----| + /// | `MINT` | `RoleMint` | 0 | + /// | `BURN` | `RoleBurn` | 1 | + /// | `NFT_CREATE` | `RoleNFTCreate` | 2 | + /// | `NFT_ADD_QUANTITY` | `RoleNFTAddQuantity` | 3 | + /// | `NFT_BURN` | `RoleNFTBurn` | 4 | + /// | `NFT_UPDATE_ATTRIBUTES`| `RoleNFTUpdateAttributes`| 5 | + /// | `NFT_ADD_URI` | `RoleNFTAddURI` | 6 | + /// | `NFT_RECREATE` | `RoleNFTRecreate` | 7 | + /// | `MODIFY_CREATOR` | `RoleModifyCreator` | 8 | + /// | `MODIFY_ROYALTIES` | `RoleModifyRoyalties` | 9 | + /// | `SET_NEW_URI` | `RoleSetNewURI` | 10 | + /// | `TRANSFER` | *(not yet in Go VM)* | 11 | + #[derive(PartialEq, Clone, Copy, Debug)] pub struct EsdtLocalRoleFlags: u64 { const NONE = 0b00000000_00000000; const MINT = 0b00000000_00000001; @@ -22,10 +44,14 @@ bitflags! { } impl EsdtLocalRoleFlags { + /// Returns `true` if this flag set contains the bit corresponding to `role`. pub fn has_role(&self, role: &EsdtLocalRole) -> bool { *self & role.to_flag() != EsdtLocalRoleFlags::NONE } + /// Iterates over all [`EsdtLocalRole`] variants whose bit is set in this flag set. + /// + /// Roles are yielded in canonical numeric-ID order (see [`EsdtLocalRole::iter_all`]). pub fn iter_roles(&self) -> impl Iterator { EsdtLocalRole::iter_all().filter(move |role| self.has_role(role)) } diff --git a/chain/core/tests/esdt_local_role_test.rs b/chain/core/tests/esdt_local_role_test.rs new file mode 100644 index 0000000000..2b11489212 --- /dev/null +++ b/chain/core/tests/esdt_local_role_test.rs @@ -0,0 +1,148 @@ +use multiversx_chain_core::types::{EsdtLocalRole, EsdtLocalRoleFlags}; + +/// Every non-None role paired with its expected numeric ID, canonical name, +/// and expected flag bit. This is the single source of truth for all +/// round-trip checks below. +#[rustfmt::skip] +const ROLE_TABLE: &[(EsdtLocalRole, u16, &str, EsdtLocalRoleFlags)] = &[ + (EsdtLocalRole::Mint, 1, "ESDTRoleLocalMint", EsdtLocalRoleFlags::MINT), + (EsdtLocalRole::Burn, 2, "ESDTRoleLocalBurn", EsdtLocalRoleFlags::BURN), + (EsdtLocalRole::NftCreate, 3, "ESDTRoleNFTCreate", EsdtLocalRoleFlags::NFT_CREATE), + (EsdtLocalRole::NftAddQuantity, 4, "ESDTRoleNFTAddQuantity", EsdtLocalRoleFlags::NFT_ADD_QUANTITY), + (EsdtLocalRole::NftBurn, 5, "ESDTRoleNFTBurn", EsdtLocalRoleFlags::NFT_BURN), + (EsdtLocalRole::NftUpdateAttributes, 6, "ESDTRoleNFTUpdateAttributes",EsdtLocalRoleFlags::NFT_UPDATE_ATTRIBUTES), + (EsdtLocalRole::NftAddUri, 7, "ESDTRoleNFTAddURI", EsdtLocalRoleFlags::NFT_ADD_URI), + (EsdtLocalRole::NftRecreate, 8, "ESDTRoleNFTRecreate", EsdtLocalRoleFlags::NFT_RECREATE), + (EsdtLocalRole::ModifyCreator, 9, "ESDTRoleModifyCreator", EsdtLocalRoleFlags::MODIFY_CREATOR), + (EsdtLocalRole::ModifyRoyalties, 10, "ESDTRoleModifyRoyalties", EsdtLocalRoleFlags::MODIFY_ROYALTIES), + (EsdtLocalRole::SetNewUri, 11, "ESDTRoleSetNewURI", EsdtLocalRoleFlags::SET_NEW_URI), + (EsdtLocalRole::Transfer, 12, "ESDTTransferRole", EsdtLocalRoleFlags::TRANSFER), +]; + +#[test] +fn test_none_role() { + assert_eq!(EsdtLocalRole::None.as_u16(), 0); + assert_eq!(EsdtLocalRole::None.name(), ""); + assert_eq!(EsdtLocalRole::None.as_role_name(), b""); + assert_eq!(EsdtLocalRole::None.to_flag(), EsdtLocalRoleFlags::NONE); +} + +#[test] +fn test_as_u16() { + for (role, id, _, _) in ROLE_TABLE { + assert_eq!(role.as_u16(), *id, "{role:?}.as_u16()"); + } +} + +#[test] +fn test_from_u16_round_trip() { + for (role, id, _, _) in ROLE_TABLE { + assert_eq!(EsdtLocalRole::from(*id), *role, "from({})", id); + } +} + +#[test] +fn test_from_u16_unknown_returns_none() { + assert_eq!(EsdtLocalRole::from(0u16), EsdtLocalRole::None); + assert_eq!(EsdtLocalRole::from(13u16), EsdtLocalRole::None); + assert_eq!(EsdtLocalRole::from(u16::MAX), EsdtLocalRole::None); +} + +/// `as_u16` and `From` must be exact inverses for every defined role. +#[test] +fn test_u16_round_trip_both_directions() { + for (role, _, _, _) in ROLE_TABLE { + let id = role.as_u16(); + assert_eq!(&EsdtLocalRole::from(id), role, "round-trip for {role:?}"); + } +} + +#[test] +fn test_name_and_as_role_name() { + for (role, _, name, _) in ROLE_TABLE { + assert_eq!(role.name(), *name, "{role:?}.name()"); + assert_eq!( + role.as_role_name(), + name.as_bytes(), + "{role:?}.as_role_name()" + ); + } +} + +#[test] +fn test_from_byte_slice_round_trip() { + for (role, _, name, _) in ROLE_TABLE { + let decoded = EsdtLocalRole::from(name.as_bytes()); + assert_eq!(decoded, *role, "from(b\"{name}\")"); + } +} + +#[test] +fn test_from_byte_slice_unknown_returns_none() { + assert_eq!(EsdtLocalRole::from(b"".as_ref()), EsdtLocalRole::None); + assert_eq!( + EsdtLocalRole::from(b"unknown".as_ref()), + EsdtLocalRole::None + ); + // Ensure a near-match (extra char) does not decode as a valid role. + assert_eq!( + EsdtLocalRole::from(b"ESDTRoleLocalMintX".as_ref()), + EsdtLocalRole::None, + ); +} + +/// `name()` and `From<&[u8]>` must be exact inverses for every defined role. +#[test] +fn test_byte_slice_round_trip_both_directions() { + for (role, _, _, _) in ROLE_TABLE { + let name = role.name(); + assert_eq!( + &EsdtLocalRole::from(name.as_bytes()), + role, + "round-trip for {role:?}", + ); + } +} + +#[test] +fn test_to_flag() { + for (role, _, _, flag) in ROLE_TABLE { + assert_eq!(role.to_flag(), *flag, "{role:?}.to_flag()"); + } + assert_eq!(EsdtLocalRole::None.to_flag(), EsdtLocalRoleFlags::NONE); +} + +/// `ModifyCreator` (9) and `ModifyRoyalties` (10) are the historically +/// swapped pair — verify both directions explicitly. +#[test] +fn test_modify_creator_and_modify_royalties_not_swapped() { + assert_eq!(EsdtLocalRole::ModifyCreator.as_u16(), 9); + assert_eq!(EsdtLocalRole::ModifyRoyalties.as_u16(), 10); + assert_eq!(EsdtLocalRole::from(9u16), EsdtLocalRole::ModifyCreator); + assert_eq!(EsdtLocalRole::from(10u16), EsdtLocalRole::ModifyRoyalties); + assert_eq!( + EsdtLocalRole::from(b"ESDTRoleModifyCreator".as_ref()), + EsdtLocalRole::ModifyCreator, + ); + assert_eq!( + EsdtLocalRole::from(b"ESDTRoleModifyRoyalties".as_ref()), + EsdtLocalRole::ModifyRoyalties, + ); + assert_eq!( + EsdtLocalRole::ModifyCreator.to_flag(), + EsdtLocalRoleFlags::MODIFY_CREATOR, + ); + assert_eq!( + EsdtLocalRole::ModifyRoyalties.to_flag(), + EsdtLocalRoleFlags::MODIFY_ROYALTIES, + ); +} + +#[test] +fn test_iter_all_order_and_completeness() { + let roles: Vec = EsdtLocalRole::iter_all().copied().collect(); + assert_eq!(roles.len(), ROLE_TABLE.len()); + for (idx, (expected_role, _, _, _)) in ROLE_TABLE.iter().enumerate() { + assert_eq!(roles[idx], *expected_role, "iter_all position {idx}"); + } +} diff --git a/chain/vm/Cargo.toml b/chain/vm/Cargo.toml index c891ac55c2..e86c14dbd6 100644 --- a/chain/vm/Cargo.toml +++ b/chain/vm/Cargo.toml @@ -27,12 +27,11 @@ wasmer-experimental = ["multiversx-chain-vm-executor-wasmer-experimental"] num-bigint = "0.4" num-traits = "0.2" hex = "0.4" -itertools = "0.14" -bitflags = "2.9" +itertools = "0.15" +bitflags = "2.13" colored = "3.0" rand = { version = "0.10", optional = true } rand_seeder = "0.5.0" -ed25519-dalek = "2.1.0" serde = { version = "1.0", features = ["derive"] } toml = "1.0" anyhow = "1.0" diff --git a/chain/vm/src/blockchain/state/account_data.rs b/chain/vm/src/blockchain/state/account_data.rs index a4e1906e07..2f9e561d1e 100644 --- a/chain/vm/src/blockchain/state/account_data.rs +++ b/chain/vm/src/blockchain/state/account_data.rs @@ -4,7 +4,7 @@ use num_traits::Zero; use super::AccountEsdt; use crate::{ display_util::key_hex, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, }; use std::{collections::HashMap, fmt, fmt::Write}; @@ -19,7 +19,7 @@ pub struct AccountData { pub storage: AccountStorage, pub username: Vec, pub contract_path: Option>, - pub code_metadata: VMCodeMetadata, + pub code_metadata: CodeMetadata, pub contract_owner: Option
, pub developer_rewards: BigUint, } @@ -34,11 +34,23 @@ impl AccountData { storage: AccountStorage::default(), username: vec![], contract_path: None, - code_metadata: VMCodeMetadata::empty(), + code_metadata: CodeMetadata::empty(), contract_owner: None, developer_rewards: BigUint::zero(), } } + + pub fn storage_get(&self, key: &[u8]) -> Vec { + self.storage.get(key).cloned().unwrap_or_default() + } + + pub fn storage_set(&mut self, key: &[u8], value: &[u8]) { + if value.is_empty() { + self.storage.remove(key); + } else { + self.storage.insert(key.to_vec(), value.to_vec()); + } + } } impl fmt::Display for AccountData { diff --git a/chain/vm/src/blockchain/state/blockchain_state.rs b/chain/vm/src/blockchain/state/blockchain_state.rs index 0df1a0e3b8..6fb44e9d1d 100644 --- a/chain/vm/src/blockchain/state/blockchain_state.rs +++ b/chain/vm/src/blockchain/state/blockchain_state.rs @@ -75,16 +75,9 @@ impl BlockchainState { .get_mut(address) .unwrap_or_else(|| panic!("Account not found: {address}")); account.egld_balance += amount; - let mut storage_v_rew = - if let Some(old_storage_value) = account.storage.get(STORAGE_REWARD_KEY) { - BigUint::from_bytes_be(old_storage_value) - } else { - BigUint::zero() - }; + let mut storage_v_rew = BigUint::from_bytes_be(&account.storage_get(STORAGE_REWARD_KEY)); storage_v_rew += amount; - account - .storage - .insert(STORAGE_REWARD_KEY.to_vec(), storage_v_rew.to_bytes_be()); + account.storage_set(STORAGE_REWARD_KEY, &storage_v_rew.to_bytes_be()); } pub fn put_new_token_identifier(&mut self, token_identifier: String) { diff --git a/chain/vm/src/builtin_functions/general/upgrade_contract.rs b/chain/vm/src/builtin_functions/general/upgrade_contract.rs index 1bbb1563c1..fb672d1e06 100644 --- a/chain/vm/src/builtin_functions/general/upgrade_contract.rs +++ b/chain/vm/src/builtin_functions/general/upgrade_contract.rs @@ -3,7 +3,7 @@ use crate::{ host::context::{BlockchainUpdate, CallType, TxCache, TxFunctionName, TxInput, TxResult}, host::execution, host::runtime::{RuntimeInstanceCallLambda, RuntimeRef}, - types::VMCodeMetadata, + types::CodeMetadata, }; use super::super::builtin_func_trait::BuiltinFunction; @@ -33,7 +33,7 @@ impl BuiltinFunction for UpgradeContract { } let new_code = tx_input.args[0].clone(); - let code_metadata = VMCodeMetadata::from(&tx_input.args[1]); + let code_metadata = CodeMetadata::from_bytes_or_default(&tx_input.args[1]); let args = if tx_input.args.len() > 2 { tx_input.args[2..].to_vec() diff --git a/chain/vm/src/crypto_functions.rs b/chain/vm/src/crypto_functions.rs index e70b10e513..e5e63ec969 100644 --- a/chain/vm/src/crypto_functions.rs +++ b/chain/vm/src/crypto_functions.rs @@ -1,36 +1,45 @@ +//! Thin wrappers over [`multiversx_chain_core::std::crypto`] for convenient use in the VM. + use multiversx_chain_core::std::crypto; +/// Computes the SHA-256 hash of `data`. +/// Returns a 32-byte digest. +/// +/// Wraps [`multiversx_chain_core::std::crypto::sha256`]. pub fn sha256(data: &[u8]) -> [u8; crypto::SHA256_RESULT_LEN] { crypto::sha256(data) } +/// Computes the Keccak-256 hash of `data`. +/// Returns a 32-byte digest. +/// +/// Wraps [`multiversx_chain_core::std::crypto::keccak256`]. pub fn keccak256(data: &[u8]) -> [u8; crypto::KECCAK256_RESULT_LEN] { crypto::keccak256(data) } +/// Verifies an Ed25519 signature. +/// +/// Wraps [`multiversx_chain_core::std::crypto::ed25519::Ed25519VerifyingKey::verify`]. +/// +/// Returns `true` if `signature` is a valid Ed25519 signature of `message` +/// under the public key `key`. +/// +/// Returns `false` if: +/// - `key` is not exactly 32 bytes +/// - `signature` is not exactly 64 bytes +/// - `key` does not represent a valid curve point +/// - the signature does not verify pub fn verify_ed25519(key: &[u8], message: &[u8], signature: &[u8]) -> bool { - use ed25519_dalek::{Signature, Verifier, VerifyingKey}; - - let key_32: [u8; 32] = if let Ok(key_32) = key.try_into() { - key_32 - } else { + let Ok(key_32) = key.try_into() else { return false; }; - let signature_64: [u8; 64] = if let Ok(signature_64) = signature.try_into() { - signature_64 - } else { + let Ok(sig_64) = signature.try_into() else { return false; }; - - let verifying_key_result = VerifyingKey::from_bytes(&key_32); - let verifying_key = if let Ok(verifying_key) = verifying_key_result { - verifying_key - } else { + let Some(verifying_key) = crypto::ed25519::Ed25519VerifyingKey::from_bytes(key_32) else { return false; }; - - let sig = Signature::from_bytes(&signature_64); - - let result = verifying_key.verify(message, &sig); - result.is_ok() + let sig = crypto::ed25519::Ed25519Signature::from_bytes(sig_64); + verifying_key.verify(message, &sig) } diff --git a/chain/vm/src/host/context/managed_type_container/tx_managed_buffer.rs b/chain/vm/src/host/context/managed_type_container/tx_managed_buffer.rs index ef1aca4dc2..13e2f5c313 100644 --- a/chain/vm/src/host/context/managed_type_container/tx_managed_buffer.rs +++ b/chain/vm/src/host/context/managed_type_container/tx_managed_buffer.rs @@ -1,6 +1,6 @@ use crate::{ host::context::{TxFunctionName, TxTokenTransfer}, - types::{Address, RawHandle, VMCodeMetadata}, + types::{Address, CodeMetadata, RawHandle}, }; use super::ManagedTypeContainer; @@ -34,9 +34,8 @@ impl ManagedTypeContainer { TxFunctionName::from(self.mb_get(handle)) } - pub fn mb_to_code_metadata(&self, handle: RawHandle) -> VMCodeMetadata { - let bytes: [u8; 2] = self.mb_get(handle).try_into().unwrap(); - VMCodeMetadata::from(bytes) + pub fn mb_to_code_metadata(&self, handle: RawHandle) -> CodeMetadata { + CodeMetadata::from_bytes_or_default(self.mb_get(handle)) } pub fn mb_get_slice( diff --git a/chain/vm/src/host/context/tx_context.rs b/chain/vm/src/host/context/tx_context.rs index ef26b188e5..1479fbf65a 100644 --- a/chain/vm/src/host/context/tx_context.rs +++ b/chain/vm/src/host/context/tx_context.rs @@ -4,7 +4,7 @@ use crate::{ state::{AccountData, AccountEsdt, BlockchainState}, }, host::runtime::RuntimeRef, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, }; use num_bigint::BigUint; use num_traits::Zero; @@ -53,7 +53,7 @@ impl TxContext { esdt: AccountEsdt::default(), username: Vec::new(), contract_path: None, - code_metadata: VMCodeMetadata::empty(), + code_metadata: CodeMetadata::empty(), contract_owner: None, developer_rewards: BigUint::zero(), }); @@ -154,7 +154,7 @@ impl TxContext { &self, new_address: &Address, contract_path: Vec, - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, contract_owner: Address, ) { assert!( diff --git a/chain/vm/src/host/execution/exec_call.rs b/chain/vm/src/host/execution/exec_call.rs index b17d0916af..c970a43290 100644 --- a/chain/vm/src/host/execution/exec_call.rs +++ b/chain/vm/src/host/execution/exec_call.rs @@ -11,7 +11,7 @@ use crate::{ }, runtime::{RuntimeInstanceCallLambda, RuntimeInstanceCallLambdaDefault, RuntimeRef}, }, - types::VMCodeMetadata, + types::CodeMetadata, }; use num_bigint::BigUint; use num_traits::Zero; @@ -210,7 +210,7 @@ fn insert_ghost_account( username: Vec::new(), storage: HashMap::new(), contract_path: None, - code_metadata: VMCodeMetadata::empty(), + code_metadata: CodeMetadata::empty(), contract_owner: None, developer_rewards: BigUint::zero(), }); diff --git a/chain/vm/src/host/execution/exec_create.rs b/chain/vm/src/host/execution/exec_create.rs index ccc370ce2e..ec49c3c06b 100644 --- a/chain/vm/src/host/execution/exec_create.rs +++ b/chain/vm/src/host/execution/exec_create.rs @@ -2,14 +2,14 @@ use crate::{ blockchain::state::BlockchainStateRef, host::context::{BlockchainUpdate, TxCache, TxContext, TxInput, TxResult}, host::runtime::{RuntimeInstanceCallLambda, RuntimeRef}, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, }; /// Executes deploy transaction and commits changes back to the underlying blockchain state. pub fn commit_deploy( tx_input: TxInput, contract_path: &[u8], - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, state: &mut BlockchainStateRef, runtime: &RuntimeRef, f: F, @@ -47,7 +47,7 @@ where pub fn execute_deploy( mut tx_input: TxInput, contract_path: Vec, - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, tx_cache: TxCache, runtime: &RuntimeRef, f: F, diff --git a/chain/vm/src/host/vm_hooks/vh_context.rs b/chain/vm/src/host/vm_hooks/vh_context.rs index bb750ae4ff..0d567de474 100644 --- a/chain/vm/src/host/vm_hooks/vh_context.rs +++ b/chain/vm/src/host/vm_hooks/vh_context.rs @@ -8,7 +8,7 @@ use crate::{ BackTransfers, ManagedTypeContainer, TxErrorTrace, TxFunctionName, TxInput, TxLog, TxResult, }, schedule::GasSchedule, - types::{Address, H256, VMCodeMetadata}, + types::{Address, CodeMetadata, H256}, }; /// Abstracts away the borrowing of a managed types structure. @@ -119,7 +119,7 @@ pub trait VMHooksContext: Debug { &mut self, egld_value: num_bigint::BigUint, contract_code: Vec, - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, args: Vec>, ) -> Result<(Address, Vec>), VMHooksEarlyExit>; diff --git a/chain/vm/src/host/vm_hooks/vh_handler/vh_blockchain.rs b/chain/vm/src/host/vm_hooks/vh_handler/vh_blockchain.rs index 333b2e71a0..e8903bcec7 100644 --- a/chain/vm/src/host/vm_hooks/vh_handler/vh_blockchain.rs +++ b/chain/vm/src/host/vm_hooks/vh_handler/vh_blockchain.rs @@ -554,7 +554,10 @@ impl VMHooksHandler { let m_types = self.context.m_types_lock(); let token_id = m_types.mb_get(token_id_handle); - Ok(multiversx_chain_core::token_identifier_util::validate_token_identifier(token_id)) + Ok( + multiversx_chain_core::token_identifier_util::validate_token_identifier(token_id) + .is_ok(), + ) } #[allow(clippy::too_many_arguments)] diff --git a/chain/vm/src/host/vm_hooks/vh_handler/vh_send.rs b/chain/vm/src/host/vm_hooks/vh_handler/vh_send.rs index eb80f6731f..2404004bf8 100644 --- a/chain/vm/src/host/vm_hooks/vh_handler/vh_send.rs +++ b/chain/vm/src/host/vm_hooks/vh_handler/vh_send.rs @@ -7,7 +7,7 @@ use crate::{ context::{AsyncCallTxData, Promise, TxFunctionName, TxTokenTransfer}, vm_hooks::{VMHooksContext, vh_early_exit::early_exit_vm_error}, }, - types::{Address, RawHandle, VMCodeMetadata, top_encode_big_uint, top_encode_u64}, + types::{Address, CodeMetadata, RawHandle, top_encode_big_uint, top_encode_u64}, vm_err_msg, }; use multiversx_chain_core::types::ReturnCode; @@ -114,7 +114,7 @@ impl VMHooksHandler { to: Address, egld_value: num_bigint::BigUint, contract_code: Vec, - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, args: Vec>, ) -> Result<(), VMHooksEarlyExit> { let mut arguments = vec![contract_code, code_metadata.to_vec()]; diff --git a/chain/vm/src/host/vm_hooks/vh_tx_context.rs b/chain/vm/src/host/vm_hooks/vh_tx_context.rs index f97960283c..a90173dadc 100644 --- a/chain/vm/src/host/vm_hooks/vh_tx_context.rs +++ b/chain/vm/src/host/vm_hooks/vh_tx_context.rs @@ -16,7 +16,7 @@ use crate::{ TxContextRef, TxFunctionName, TxInput, TxResult, async_call_tx_input, }, host::execution, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, vm_err_msg, }; @@ -99,9 +99,8 @@ impl VMHooksContext for TxVMHooksContext { } fn storage_read_any_address(&self, address: &Address, key: &[u8]) -> Vec { - self.tx_context_ref.with_account_mut(address, |account| { - account.storage.get(key).cloned().unwrap_or_default() - }) + self.tx_context_ref + .with_account_mut(address, |account| account.storage_get(key)) } fn storage_write(&mut self, key: &[u8], value: &[u8]) -> Result<(), VMHooksEarlyExit> { @@ -109,7 +108,7 @@ impl VMHooksContext for TxVMHooksContext { self.check_not_readonly()?; self.tx_context_ref.with_contract_account_mut(|account| { - account.storage.insert(key.to_vec(), value.to_vec()); + account.storage_set(key, value); }); Ok(()) } @@ -209,7 +208,7 @@ impl VMHooksContext for TxVMHooksContext { &mut self, egld_value: num_bigint::BigUint, contract_code: Vec, - code_metadata: VMCodeMetadata, + code_metadata: CodeMetadata, args: Vec>, ) -> Result<(Address, Vec>), VMHooksEarlyExit> { let contract_address = self.current_address(); diff --git a/chain/vm/src/types.rs b/chain/vm/src/types.rs index e584909d0e..573563ed25 100644 --- a/chain/vm/src/types.rs +++ b/chain/vm/src/types.rs @@ -1,5 +1,5 @@ pub use crate::chain_core::types::Address; -pub use crate::chain_core::types::CodeMetadata as VMCodeMetadata; +pub use crate::chain_core::types::CodeMetadata; pub use crate::chain_core::types::EsdtLocalRole; pub use crate::chain_core::types::EsdtLocalRoleFlags; pub use crate::chain_core::types::H256; diff --git a/contracts/benchmarks/large-storage/Cargo.toml b/contracts/benchmarks/large-storage/Cargo.toml index a8b3e2c826..a468db08cd 100644 --- a/contracts/benchmarks/large-storage/Cargo.toml +++ b/contracts/benchmarks/large-storage/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/large_storage.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/large-storage/meta/Cargo.toml b/contracts/benchmarks/large-storage/meta/Cargo.toml index 9cadd39896..443294c1a3 100644 --- a/contracts/benchmarks/large-storage/meta/Cargo.toml +++ b/contracts/benchmarks/large-storage/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/benchmark-common/Cargo.toml b/contracts/benchmarks/mappers/benchmark-common/Cargo.toml index f7fa205a89..d7c06fc6c4 100644 --- a/contracts/benchmarks/mappers/benchmark-common/Cargo.toml +++ b/contracts/benchmarks/mappers/benchmark-common/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/linked-list-repeat/Cargo.toml b/contracts/benchmarks/mappers/linked-list-repeat/Cargo.toml index c942e012c2..aa81a41863 100644 --- a/contracts/benchmarks/mappers/linked-list-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/linked-list-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/linked-list-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/linked-list-repeat/meta/Cargo.toml index 3f4d333edc..8e86dafb85 100644 --- a/contracts/benchmarks/mappers/linked-list-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/linked-list-repeat/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/map-repeat/Cargo.toml b/contracts/benchmarks/mappers/map-repeat/Cargo.toml index 3390127a43..9a64ed2215 100644 --- a/contracts/benchmarks/mappers/map-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/map-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/map-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/map-repeat/meta/Cargo.toml index 83a5437593..4ca3374861 100644 --- a/contracts/benchmarks/mappers/map-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/map-repeat/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/queue-repeat/Cargo.toml b/contracts/benchmarks/mappers/queue-repeat/Cargo.toml index 5a4d7dd029..95a448899a 100644 --- a/contracts/benchmarks/mappers/queue-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/queue-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/queue-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/queue-repeat/meta/Cargo.toml index 14e79e81d7..1c072b24ee 100644 --- a/contracts/benchmarks/mappers/queue-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/queue-repeat/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/set-repeat/Cargo.toml b/contracts/benchmarks/mappers/set-repeat/Cargo.toml index 0835ae58e4..593998d9cb 100644 --- a/contracts/benchmarks/mappers/set-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/set-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/set-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/set-repeat/meta/Cargo.toml index 53489a69e3..f92eff3344 100644 --- a/contracts/benchmarks/mappers/set-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/set-repeat/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/single-value-repeat/Cargo.toml b/contracts/benchmarks/mappers/single-value-repeat/Cargo.toml index 03302cc8bb..94b44613fa 100644 --- a/contracts/benchmarks/mappers/single-value-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/single-value-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/single-value-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/single-value-repeat/meta/Cargo.toml index 56b1fe93ca..25748b8837 100644 --- a/contracts/benchmarks/mappers/single-value-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/single-value-repeat/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/mappers/vec-repeat/Cargo.toml b/contracts/benchmarks/mappers/vec-repeat/Cargo.toml index 7eea00f008..d828d90ea3 100644 --- a/contracts/benchmarks/mappers/vec-repeat/Cargo.toml +++ b/contracts/benchmarks/mappers/vec-repeat/Cargo.toml @@ -13,9 +13,7 @@ path = "../benchmark-common" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/mappers/vec-repeat/meta/Cargo.toml b/contracts/benchmarks/mappers/vec-repeat/meta/Cargo.toml index 50dc6a532b..359a287d07 100644 --- a/contracts/benchmarks/mappers/vec-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/mappers/vec-repeat/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/send-tx-repeat/Cargo.toml b/contracts/benchmarks/send-tx-repeat/Cargo.toml index feaab832e5..ebaab483b9 100644 --- a/contracts/benchmarks/send-tx-repeat/Cargo.toml +++ b/contracts/benchmarks/send-tx-repeat/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/send_tx_repeat.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/send-tx-repeat/meta/Cargo.toml b/contracts/benchmarks/send-tx-repeat/meta/Cargo.toml index cfa1286282..ea0989eb4f 100644 --- a/contracts/benchmarks/send-tx-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/send-tx-repeat/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/str-repeat/Cargo.toml b/contracts/benchmarks/str-repeat/Cargo.toml index 5b7f8b8767..6fa7cdb54c 100644 --- a/contracts/benchmarks/str-repeat/Cargo.toml +++ b/contracts/benchmarks/str-repeat/Cargo.toml @@ -12,10 +12,8 @@ path = "src/str_repeat.rs" managed-buffer-builder-cached = ["multiversx-sc/managed-buffer-builder-cached"] [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/str-repeat/meta/Cargo.toml b/contracts/benchmarks/str-repeat/meta/Cargo.toml index 74896ce7d1..9a8ce52c5c 100644 --- a/contracts/benchmarks/str-repeat/meta/Cargo.toml +++ b/contracts/benchmarks/str-repeat/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/benchmarks/very-large-storage/Cargo.toml b/contracts/benchmarks/very-large-storage/Cargo.toml index cc09504691..bdcfba21ab 100644 --- a/contracts/benchmarks/very-large-storage/Cargo.toml +++ b/contracts/benchmarks/very-large-storage/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/very_large_storage.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/benchmarks/very-large-storage/interactor/Cargo.toml b/contracts/benchmarks/very-large-storage/interactor/Cargo.toml index 90570839f6..12e9328b60 100644 --- a/contracts/benchmarks/very-large-storage/interactor/Cargo.toml +++ b/contracts/benchmarks/very-large-storage/interactor/Cargo.toml @@ -16,8 +16,7 @@ path = "src/vls_interactor.rs" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/contracts/benchmarks/very-large-storage/interactor/config.toml b/contracts/benchmarks/very-large-storage/interactor/config.toml index 64846243cc..7be0aaaee5 100644 --- a/contracts/benchmarks/very-large-storage/interactor/config.toml +++ b/contracts/benchmarks/very-large-storage/interactor/config.toml @@ -1,2 +1,6 @@ +[connection] chain_type = 'simulator' gateway_uri = 'http://localhost:8085' + +[owner] +test_wallet = 'mike' diff --git a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor.rs b/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor.rs index 3658f06d54..578e832872 100644 --- a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor.rs +++ b/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor.rs @@ -1,22 +1,48 @@ mod vls_interactor_cli; -mod vls_interactor_config; -mod vls_interactor_state; use clap::Parser; +use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; use very_large_storage::very_large_storage_proxy; -pub use vls_interactor_config::Config; -use vls_interactor_state::State; -use multiversx_sc_snippets::imports::*; +/// Very Large Storage Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub connection: ConnectionConfig, + pub owner: WalletConfig, +} + +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + vec![self.owner.wallet().clone()] + } +} + +/// Very Large Storage Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + pub contract_address: Option, +} + +impl State { + /// Returns the contract address + pub fn current_contract_address(&self) -> &Bech32Address { + self.contract_address + .as_ref() + .expect("no known contract, deploy first") + } +} const CODE_PATH: MxscPath = MxscPath::new("../output/very-large-storage.mxsc.json"); pub async fn very_large_storage_cli() { env_logger::init(); - let config = Config::load_config(); - - let mut basic_interact = VeryLargeStorageInteractor::new(config).await; + let mut basic_interact = VeryLargeStorageInteractor::new().await; let cli = vls_interactor_cli::InteractCli::parse(); match &cli.command { @@ -32,34 +58,28 @@ pub async fn very_large_storage_cli() { pub struct VeryLargeStorageInteractor { pub interactor: Interactor, - pub owner_address: Bech32Address, - pub state: State, + pub config: Config, + pub state: AutoSave, } impl VeryLargeStorageInteractor { - pub async fn new(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()); - interactor - .set_current_dir_from_workspace("contracts/benchmarks/very-large-storage/interactor"); - - let owner_address = interactor.register_wallet(test_wallets::mike()).await; - - interactor.generate_blocks(30u64).await.unwrap(); - - VeryLargeStorageInteractor { + pub async fn new() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + Self { interactor, - owner_address: owner_address.into(), - state: State::load_state(), + config, + state, } } pub async fn deploy(&mut self) { + let owner_address = self.config.owner.address(); let new_address = self .interactor .tx() - .from(&self.owner_address) + .from(&owner_address) .gas(10_000_000u64) .typed(very_large_storage_proxy::VeryLargeStorageProxy) .init() @@ -69,14 +89,15 @@ impl VeryLargeStorageInteractor { .await; println!("new address: {new_address}"); - self.state.set_contract_address(new_address); + self.state.contract_address = Some(new_address); } pub async fn append(&mut self, num_bytes: u64) { + let owner_address = self.config.owner.address(); let gas_used = self .interactor .tx() - .from(&self.owner_address) + .from(&owner_address) .to(self.state.current_contract_address()) .gas(SimulateGas) .typed(very_large_storage_proxy::VeryLargeStorageProxy) diff --git a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_config.rs b/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_config.rs deleted file mode 100644 index d9802f6436..0000000000 --- a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_config.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_state.rs b/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_state.rs deleted file mode 100644 index e606fb445a..0000000000 --- a/contracts/benchmarks/very-large-storage/interactor/src/vls_interactor_state.rs +++ /dev/null @@ -1,48 +0,0 @@ -use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - contract_address: Option, -} - -impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - pub fn set_contract_address(&mut self, address: Bech32Address) { - self.contract_address = Some(address); - } - - /// Returns the contract address - pub fn current_contract_address(&self) -> &Bech32Address { - self.contract_address - .as_ref() - .expect("no known contract, deploy first") - } -} - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/benchmarks/very-large-storage/meta/Cargo.toml b/contracts/benchmarks/very-large-storage/meta/Cargo.toml index ce75db5bc8..c070eb20f9 100644 --- a/contracts/benchmarks/very-large-storage/meta/Cargo.toml +++ b/contracts/benchmarks/very-large-storage/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/core/price-aggregator/Cargo.toml b/contracts/core/price-aggregator/Cargo.toml index 527875be58..30c4037db4 100644 --- a/contracts/core/price-aggregator/Cargo.toml +++ b/contracts/core/price-aggregator/Cargo.toml @@ -19,16 +19,13 @@ edition = "2024" path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true [dev-dependencies] rand = { version = "0.10" } diff --git a/contracts/core/price-aggregator/meta/Cargo.toml b/contracts/core/price-aggregator/meta/Cargo.toml index c2d1de3082..df6f218765 100644 --- a/contracts/core/price-aggregator/meta/Cargo.toml +++ b/contracts/core/price-aggregator/meta/Cargo.toml @@ -8,10 +8,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/core/wegld-swap/Cargo.toml b/contracts/core/wegld-swap/Cargo.toml index c1b7727430..65a9cc8b1a 100644 --- a/contracts/core/wegld-swap/Cargo.toml +++ b/contracts/core/wegld-swap/Cargo.toml @@ -20,13 +20,10 @@ edition = "2024" path = "src/wegld.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/core/wegld-swap/meta/Cargo.toml b/contracts/core/wegld-swap/meta/Cargo.toml index baa6623eb4..eeb60b10b6 100644 --- a/contracts/core/wegld-swap/meta/Cargo.toml +++ b/contracts/core/wegld-swap/meta/Cargo.toml @@ -11,10 +11,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/adder/Cargo.toml b/contracts/examples/adder/Cargo.toml index a385e8d948..06a8c11b70 100644 --- a/contracts/examples/adder/Cargo.toml +++ b/contracts/examples/adder/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/adder.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/examples/adder/interactor/Cargo.toml b/contracts/examples/adder/interactor/Cargo.toml index 0822398439..f64fa0c2c3 100644 --- a/contracts/examples/adder/interactor/Cargo.toml +++ b/contracts/examples/adder/interactor/Cargo.toml @@ -1,23 +1,26 @@ [package] -name = "basic-interactor" +name = "adder-interactor" version = "0.0.0" authors = ["MultiversX "] edition = "2024" publish = false [[bin]] -name = "basic-interactor" -path = "src/basic_interactor_main.rs" +name = "adder-interactor" +path = "src/adder_interactor_main.rs" [lib] -path = "src/basic_interactor.rs" +path = "src/adder_interactor.rs" + +[features] +ledger = ["multiversx-sc-snippets/ledger"] +chain-simulator-tests = [] [dependencies.adder] path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } @@ -27,6 +30,3 @@ tokio = { version = "1.24" } [dev-dependencies] serial_test = "3.2" - -[features] -chain-simulator-tests = [] diff --git a/contracts/examples/adder/interactor/config.toml b/contracts/examples/adder/interactor/config.toml index 97acd5a5c6..a099214ce9 100644 --- a/contracts/examples/adder/interactor/config.toml +++ b/contracts/examples/adder/interactor/config.toml @@ -1,7 +1,36 @@ +[general] +contract_path = '../output/adder.mxsc.json' +[connection] # chain_type = 'simulator' # gateway_uri = 'http://localhost:8085' chain_type = 'real' gateway_uri = 'https://devnet-gateway.multiversx.com' +# Owner wallet - used for deploying and upgrading the contract. +# Provide exactly one of: test_wallet, pem, or keyfile. +[owner] +test_wallet = 'mike' +# pem = 'path/to/owner.pem' +# keyfile = 'path/to/owner.json' +# keystore_password = 'password' + +# User wallet - used for calling contract endpoints. +# Provide exactly one of: test_wallet, pem, or keyfile. +# Can be the same as the owner wallet if desired. +[wallet] +test_wallet = 'ivan' +# pem = 'path/to/wallet.pem' +# keyfile = 'path/to/wallet.json' +# keystore_password = 'password' + +[ledger-wallet] +ledger = true + +# Optional relayer wallet, used in `add-relayed`. +[relayer] +test_wallet = 's1mon' # ivan is in shard 1 +# pem = 'path/to/relayer.pem' +# keyfile = 'path/to/relayer.json' +# keystore_password = 'password' diff --git a/contracts/examples/adder/interactor/src/adder_interactor.rs b/contracts/examples/adder/interactor/src/adder_interactor.rs new file mode 100644 index 0000000000..8b1183c298 --- /dev/null +++ b/contracts/examples/adder/interactor/src/adder_interactor.rs @@ -0,0 +1,231 @@ +mod adder_interactor_cli; + +use adder::adder_proxy; +use clap::Parser; +use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; + +/// Adder Interact general settings +#[derive(Debug, Deserialize)] +pub struct GeneralConfig { + pub contract_path: ConfigPath, +} + +/// Adder Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub general: GeneralConfig, + pub connection: ConnectionConfig, + pub owner: WalletConfig, + pub wallet: WalletConfig, + #[serde(rename = "ledger-wallet")] + pub ledger_wallet: Option, + pub relayer: Option, +} + +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + let mut wallets = vec![self.owner.wallet().clone(), self.wallet.wallet().clone()]; + #[cfg(feature = "ledger")] + if let Some(ledger_wallet) = &self.ledger_wallet { + wallets.push(ledger_wallet.wallet().clone()); + } + if let Some(relayer) = &self.relayer { + wallets.push(relayer.wallet().clone()); + } + wallets + } +} + +/// Adder Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + pub adder_address: Option, +} + +impl State { + /// Returns the adder contract + pub fn current_adder_address(&self) -> &Bech32Address { + self.adder_address + .as_ref() + .expect("no known adder contract, deploy first") + } +} + +pub async fn adder_cli() { + env_logger::init(); + + let mut adder_interact = AdderInteractor::from_config().await; + + let cli = adder_interactor_cli::InteractCli::parse(); + match &cli.command { + Some(adder_interactor_cli::InteractCliCommand::Deploy) => { + adder_interact.deploy().await; + } + Some(adder_interactor_cli::InteractCliCommand::Upgrade(args)) => { + let owner_address = adder_interact.config.owner.address(); + adder_interact + .upgrade(args.value, &owner_address, None) + .await + } + Some(adder_interactor_cli::InteractCliCommand::Sum) => { + let sum = adder_interact.get_sum().await; + println!("sum: {sum}"); + } + Some(adder_interactor_cli::InteractCliCommand::Add(args)) => { + adder_interact.add(args.value).await; + } + Some(adder_interactor_cli::InteractCliCommand::AddRelayed(args)) => { + adder_interact.add_relayed(args.value).await; + } + Some(adder_interactor_cli::InteractCliCommand::AddLedger(args)) => { + adder_interact.add_ledger(args.value).await; + } + None => {} + } +} + +pub struct AdderInteractor { + pub interactor: Interactor, + pub config: Config, + pub state: AutoSave, +} + +impl AdderInteractor { + pub async fn from_config() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + AdderInteractor { + interactor, + config, + state, + } + } + + pub async fn generate_blocks(&self, num_blocks: i32) { + self.interactor + .generate_blocks(num_blocks as u64) + .await + .unwrap(); + } + + pub async fn deploy(&mut self) { + let owner_address = self.config.owner.address(); + let new_address = self + .interactor + .tx() + .id("interactor deploy") + .from(&owner_address) + .gas(100_000_000) + .typed(adder_proxy::AdderProxy) + .init(0u64) + .code(&self.config.general.contract_path) + .returns(ReturnsNewBech32Address) + .run() + .await; + + println!("new address: {new_address}"); + self.state.adder_address = Some(new_address); + } + + pub async fn upgrade(&mut self, new_value: u32, sender: &Bech32Address, err: Option<&str>) { + let response = self + .interactor + .tx() + .from(sender) + .to(self.state.current_adder_address()) + .gas(6_000_000) + .typed(adder_proxy::AdderProxy) + .upgrade(new_value) + .code(&self.config.general.contract_path) + .code_metadata(CodeMetadata::UPGRADEABLE) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => { + println!("Contract successfully upgraded."); + } + Err(tx_err) => { + println!("Contract failed upgrade with error: {}", tx_err.message); + assert_eq!(tx_err.message, err.unwrap_or_default()); + } + } + } + + pub async fn get_sum(&mut self) -> RustBigUint { + self.interactor + .query() + .to(self.state.current_adder_address()) + .typed(adder_proxy::AdderProxy) + .sum() + .returns(ReturnsResultUnmanaged) + .run() + .await + } + + pub async fn add(&mut self, value: u32) { + self.interactor + .tx() + .id("interactor add") + .from(self.config.wallet.address()) + .to(self.state.current_adder_address()) + .gas(6_000_000u64) + .typed(adder_proxy::AdderProxy) + .add(value) + .run() + .await; + + println!("Successfully performed add"); + } + + pub async fn add_ledger(&mut self, value: u32) { + let ledger_wallet = self + .config + .ledger_wallet + .as_ref() + .expect("ledger wallet not configured"); + self.interactor + .tx() + .id("interactor add") + .from(ledger_wallet.address()) + .to(self.state.current_adder_address()) + .gas(6_000_000u64) + .typed(adder_proxy::AdderProxy) + .add(value) + .run() + .await; + + println!("Successfully performed add via ledger"); + } + + /// Same as `add`, but uses the configured relayer to pay the gas. + /// Requires `[relayer]` to be set in config.toml. + /// The gas limit includes an extra base cost (50 000) for the relay operation. + pub async fn add_relayed(&mut self, value: u32) { + let relayer = self + .config + .relayer + .as_ref() + .expect("relayer expected in config"); + self.interactor + .tx() + .id("interactor add relayed") + .from(self.config.wallet.address()) + .relayer(relayer.address()) + .to(self.state.current_adder_address()) + .gas(6_050_000u64) + .typed(adder_proxy::AdderProxy) + .add(value) + .run() + .await; + + println!("Successfully performed relayed add"); + } +} diff --git a/contracts/examples/adder/interactor/src/basic_interactor_cli.rs b/contracts/examples/adder/interactor/src/adder_interactor_cli.rs similarity index 82% rename from contracts/examples/adder/interactor/src/basic_interactor_cli.rs rename to contracts/examples/adder/interactor/src/adder_interactor_cli.rs index 3a4a98e502..5a0c8ededb 100644 --- a/contracts/examples/adder/interactor/src/basic_interactor_cli.rs +++ b/contracts/examples/adder/interactor/src/adder_interactor_cli.rs @@ -20,6 +20,13 @@ pub enum InteractCliCommand { Sum, #[command(name = "add", about = "Add value")] Add(AddArgs), + #[command(name = "add-ledger", about = "Add value via ledger device")] + AddLedger(AddArgs), + #[command( + name = "add-relayed", + about = "Add value via a relayed transaction (relayer pays gas)" + )] + AddRelayed(AddArgs), } #[derive(Default, Clone, PartialEq, Eq, Debug, Args)] diff --git a/contracts/examples/adder/interactor/src/adder_interactor_main.rs b/contracts/examples/adder/interactor/src/adder_interactor_main.rs new file mode 100644 index 0000000000..538f55ba07 --- /dev/null +++ b/contracts/examples/adder/interactor/src/adder_interactor_main.rs @@ -0,0 +1,6 @@ +extern crate adder_interactor; + +#[tokio::main] +pub async fn main() { + adder_interactor::adder_cli().await; +} diff --git a/contracts/examples/adder/interactor/src/basic_interactor.rs b/contracts/examples/adder/interactor/src/basic_interactor.rs deleted file mode 100644 index e587e18bce..0000000000 --- a/contracts/examples/adder/interactor/src/basic_interactor.rs +++ /dev/null @@ -1,146 +0,0 @@ -mod basic_interactor_cli; -mod basic_interactor_config; -mod basic_interactor_state; - -use adder::adder_proxy; -pub use basic_interactor_config::Config; -use basic_interactor_state::State; -use clap::Parser; - -use multiversx_sc_snippets::imports::*; - -const ADDER_CODE_PATH: MxscPath = MxscPath::new("../output/adder.mxsc.json"); - -pub async fn adder_cli() { - env_logger::init(); - - let config = Config::load_config(); - - let mut basic_interact = BasicInteractor::new(config).await; - - let cli = basic_interactor_cli::InteractCli::parse(); - match &cli.command { - Some(basic_interactor_cli::InteractCliCommand::Deploy) => { - basic_interact.deploy().await; - } - Some(basic_interactor_cli::InteractCliCommand::Upgrade(args)) => { - let owner_address = basic_interact.adder_owner_address.clone(); - basic_interact - .upgrade(args.value, &owner_address, None) - .await - } - Some(basic_interactor_cli::InteractCliCommand::Add(args)) => { - basic_interact.add(args.value).await; - } - Some(basic_interactor_cli::InteractCliCommand::Sum) => { - let sum = basic_interact.get_sum().await; - println!("sum: {sum}"); - } - None => {} - } -} - -pub struct BasicInteractor { - pub interactor: Interactor, - pub adder_owner_address: Bech32Address, - pub wallet_address: Bech32Address, - pub state: State, -} - -impl BasicInteractor { - pub async fn new(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()); - interactor.set_current_dir_from_workspace("contracts/examples/adder/interactor"); - - let adder_owner_address = interactor.register_wallet(test_wallets::mike()).await; - let wallet_address = interactor.register_wallet(test_wallets::ivan()).await; - - interactor.generate_blocks(30u64).await.unwrap(); - - BasicInteractor { - interactor, - adder_owner_address: adder_owner_address.into(), - wallet_address: wallet_address.into(), - state: State::load_state(), - } - } - - pub async fn generate_blocks(&self, num_blocks: i32) { - self.interactor - .generate_blocks(num_blocks as u64) - .await - .unwrap(); - } - - pub async fn deploy(&mut self) { - let new_address = self - .interactor - .tx() - .id("interactor deploy") - .from(&self.adder_owner_address.clone()) - .gas(100_000_000) - .typed(adder_proxy::AdderProxy) - .init(0u64) - .code(ADDER_CODE_PATH) - .returns(ReturnsNewBech32Address) - .run() - .await; - - println!("new address: {new_address}"); - self.state.set_adder_address(new_address); - } - - pub async fn upgrade(&mut self, new_value: u32, sender: &Bech32Address, err: Option<&str>) { - let response = self - .interactor - .tx() - .from(sender) - .to(self.state.current_adder_address()) - .gas(6_000_000) - .typed(adder_proxy::AdderProxy) - .upgrade(new_value) - .code(ADDER_CODE_PATH) - .code_metadata(CodeMetadata::UPGRADEABLE) - .returns(ReturnsHandledOrError::new()) - .run() - .await; - - match response { - Ok(_) => { - println!("Contract successfully upgraded."); - } - Err(tx_err) => { - println!("Contract failed upgrade with error: {}", tx_err.message); - assert_eq!(tx_err.message, err.unwrap_or_default()); - } - } - } - - pub async fn add(&mut self, value: u32) { - self.interactor - .tx() - .id("interactor add") - .from(&self.wallet_address) - .to(self.state.current_adder_address()) - .gas(6_000_000u64) - .typed(adder_proxy::AdderProxy) - .add(value) - .run() - .await; - - println!("Successfully performed add"); - } - - pub async fn get_sum(&mut self) -> RustBigUint { - self.interactor - .query() - .to(self.state.current_adder_address()) - .typed(adder_proxy::AdderProxy) - .sum() - .returns(ReturnsResultUnmanaged) - .run() - .await - } -} diff --git a/contracts/examples/adder/interactor/src/basic_interactor_config.rs b/contracts/examples/adder/interactor/src/basic_interactor_config.rs deleted file mode 100644 index bd19da629d..0000000000 --- a/contracts/examples/adder/interactor/src/basic_interactor_config.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Adder Interact configuration -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/examples/adder/interactor/src/basic_interactor_main.rs b/contracts/examples/adder/interactor/src/basic_interactor_main.rs deleted file mode 100644 index 72c8291332..0000000000 --- a/contracts/examples/adder/interactor/src/basic_interactor_main.rs +++ /dev/null @@ -1,6 +0,0 @@ -extern crate basic_interactor; - -#[tokio::main] -pub async fn main() { - basic_interactor::adder_cli().await; -} diff --git a/contracts/examples/adder/interactor/src/basic_interactor_state.rs b/contracts/examples/adder/interactor/src/basic_interactor_state.rs deleted file mode 100644 index bcab774134..0000000000 --- a/contracts/examples/adder/interactor/src/basic_interactor_state.rs +++ /dev/null @@ -1,50 +0,0 @@ -use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; - -/// Adder Interact state -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - adder_address: Option, -} - -impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - /// Sets the adder address - pub fn set_adder_address(&mut self, address: Bech32Address) { - self.adder_address = Some(address); - } - - /// Returns the adder contract - pub fn current_adder_address(&self) -> &Bech32Address { - self.adder_address - .as_ref() - .expect("no known adder contract, deploy first") - } -} - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/examples/adder/interactor/tests/adder_interactor_cs_test.rs b/contracts/examples/adder/interactor/tests/adder_interactor_cs_test.rs new file mode 100644 index 0000000000..0ec88400b1 --- /dev/null +++ b/contracts/examples/adder/interactor/tests/adder_interactor_cs_test.rs @@ -0,0 +1,83 @@ +use adder_interactor::{AdderInteractor, Config, GeneralConfig}; +use multiversx_sc_snippets::imports::*; +use serial_test::serial; + +fn chain_simulator_config() -> Config { + Config { + general: GeneralConfig { + contract_path: "../output/adder.mxsc.json".into(), + }, + connection: ConnectionConfig::chain_simulator(), + owner: WalletConfig::from_test_wallet("mike"), + wallet: WalletConfig::from_test_wallet("ivan"), + ledger_wallet: None, + relayer: Some(WalletConfig::from_test_wallet("s1mon")), + } +} + +async fn test_interactor() -> AdderInteractor { + let config = chain_simulator_config(); + let interactor = Interactor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&config) + .await; + AdderInteractor { + interactor, + config, + state: AutoSave::no_save_default(), + } +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn deploy_add_upgrade_test() { + let mut adder_interact = test_interactor().await; + + adder_interact.generate_blocks(2).await; + + adder_interact.deploy().await; + + adder_interact.generate_blocks(2).await; + + adder_interact.add(1u32).await; + + adder_interact.generate_blocks(2).await; + + // Sum will be 1 + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 1u32.into()); + + adder_interact + .upgrade(7u32, &adder_interact.config.owner.address(), None) + .await; + + adder_interact.generate_blocks(2).await; + + // Sum will be the updated value of 7 + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 7u32.into()); + + // Upgrade fails + adder_interact + .upgrade( + 10u32, + &adder_interact.config.wallet.address(), + Some("upgrade is allowed only for owner"), + ) + .await; + + adder_interact.generate_blocks(2).await; + + // Sum will remain 7 + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 7u32.into()); + + adder_interact.add_relayed(3u32).await; + + adder_interact.generate_blocks(2).await; + + // Sum will be 7 + 3 = 10 + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 10u32.into()); +} diff --git a/contracts/examples/adder/interactor/tests/adder_interactor_ledger_test.rs b/contracts/examples/adder/interactor/tests/adder_interactor_ledger_test.rs new file mode 100644 index 0000000000..c5aec4d4e9 --- /dev/null +++ b/contracts/examples/adder/interactor/tests/adder_interactor_ledger_test.rs @@ -0,0 +1,78 @@ +//! Note: Currently not included in the template. +#![cfg(feature = "ledger")] + +use adder_interactor::{AdderInteractor, Config, GeneralConfig}; +use multiversx_sc_snippets::imports::*; +use multiversx_sc_snippets::sdk::wallet::ledger::{LedgerApp, WalletTransport}; +use serial_test::serial; + +/// Builds a chain-simulator config that uses carol's test wallet as the mock +/// ledger signer. `WalletTransport::new(carol)` simulates the Ledger app at +/// the APDU level; the interactor uses the same backing private key for signing. +fn chain_simulator_ledger_config() -> Config { + Config { + general: GeneralConfig { + contract_path: "../output/adder.mxsc.json".into(), + }, + connection: ConnectionConfig::chain_simulator(), + owner: WalletConfig::from_test_wallet("mike"), + wallet: WalletConfig::from_test_wallet("ivan"), + ledger_wallet: Some(WalletConfig::from_test_wallet("carol")), + relayer: None, + } +} + +async fn test_interactor() -> AdderInteractor { + let config = chain_simulator_ledger_config(); + let interactor = Interactor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&config) + .await; + AdderInteractor { + interactor, + config, + state: AutoSave::no_save_default(), + } +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn add_ledger_mock_test() { + // Verify that WalletTransport correctly simulates Ledger address lookup. + // In production the interactor would call LedgerApp::new() to open the + // physical device; here we inject WalletTransport so no hardware is needed. + let carol = test_wallets::carol(); + let transport = WalletTransport::new(carol.clone()); + let mut mock_app = LedgerApp::with_transport(Box::new(transport)); + let ledger_addr = mock_app.get_address(0).unwrap(); + assert_eq!( + ledger_addr, + carol.to_bech32().to_string(), + "WalletTransport must report the same address as its backing wallet" + ); + + let mut adder_interact = test_interactor().await; + + adder_interact.generate_blocks(2).await; + + adder_interact.deploy().await; + + adder_interact.generate_blocks(2).await; + + // add_ledger sends the transaction from carol's address (the mock ledger wallet). + adder_interact.add_ledger(5u32).await; + + adder_interact.generate_blocks(2).await; + + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 5u32.into()); + + // A second add to confirm accumulation. + adder_interact.add_ledger(3u32).await; + + adder_interact.generate_blocks(2).await; + + let sum = adder_interact.get_sum().await; + assert_eq!(sum, 8u32.into()); +} diff --git a/contracts/examples/adder/meta/Cargo.toml b/contracts/examples/adder/meta/Cargo.toml index f09422eae0..aa024c4e35 100644 --- a/contracts/examples/adder/meta/Cargo.toml +++ b/contracts/examples/adder/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/adder/mxsc-template.toml b/contracts/examples/adder/mxsc-template.toml index 3609879fef..218a267aff 100644 --- a/contracts/examples/adder/mxsc-template.toml +++ b/contracts/examples/adder/mxsc-template.toml @@ -25,7 +25,7 @@ files_include = [ "interactor/config.toml", "interactor/.gitignore", "interactor/src", - "interactor/tests", + "interactor/tests/adder_interactor_cs_test.rs", "snippets/adder.snippets.sh", ] has_interactor = true diff --git a/contracts/examples/adder/snippets/adder.snippets.sh b/contracts/examples/adder/snippets/adder.snippets.sh index ea048346ad..4a22b74dc1 100644 --- a/contracts/examples/adder/snippets/adder.snippets.sh +++ b/contracts/examples/adder/snippets/adder.snippets.sh @@ -14,10 +14,12 @@ DATA_TOOL=("${BASE[@]}" data) # ────────────────────────────────────────────────────────────────────────────── PEM="alice.pem" +RELAYER_PEM="s1mon.pem" # Creates a test wallet (alice.pem). Only works with sc-meta. setup() { sc-meta wallet test-wallet --name alice + sc-meta wallet test-wallet --name s1mon # will act as the relayer } case "${NETWORK}" in @@ -123,3 +125,80 @@ add_v2() { --infile="${OUTFILE_CALL_SIGNED}" \ || return } + +# Signs the add call with a Ledger hardware wallet instead of a PEM file. +# Uses --ledger (address index 0 by default); override with LEDGER_ADDRESS_INDEX. +add_ledger() { + NUMBER=5 + LEDGER_ADDRESS_INDEX="${LEDGER_ADDRESS_INDEX:-0}" + + "${TX_TOOL[@]}" call "${ADDRESS}" \ + --ledger \ + --sender-wallet-index "${LEDGER_ADDRESS_INDEX}" \ + --gas-limit=5000000 \ + --function="add" \ + --arguments "${NUMBER}" \ + --proxy="${PROXY}" \ + --chain="${CHAIN}" \ + --send \ + --outfile="${OUTFILE_CALL}" \ + --wait-result \ + || return +} + +# Demonstrates relayed v3 transactions (sc-meta only): +# Bob (relayer) pays the gas so Alice (sender) needs no EGLD for fees. +# +# Two-step flow: +# 1. Alice builds + signs the call with --relayer pointing to Bob's address. +# The gas limit includes an extra base cost (50 000) for the relay operation. +# The transaction is saved to a file — not broadcast yet. +# 2. Bob signs as relayer with `tx relay` and broadcasts. +add_relayed() { + NUMBER=5 + RELAYER_ADDRESS="erd1fwdty5j20525zmah34xcsfyacvp890twuxu2ql2rgtpn5s9qqqqsajtqv5" + OUTFILE_CALL_UNSIGNED="call-unsigned-${NETWORK}.interaction.json" + OUTFILE_CALL_RELAYED="call-relayed-${NETWORK}.interaction.json" + + # Step 1: Alice signs (gas = 5 000 000 execution + 50 000 extra relay base cost). + "${TX_TOOL[@]}" call "${ADDRESS}" \ + --pem="${PEM}" \ + --gas-limit=5050000 \ + --function="add" \ + --arguments "${NUMBER}" \ + --relayer="${RELAYER_ADDRESS}" \ + --proxy="${PROXY}" \ + --chain="${CHAIN}" \ + --outfile="${OUTFILE_CALL_UNSIGNED}" \ + || return + + # Step 2: Bob adds his relayer signature and broadcasts. + "${BASE[@]}" tx relay \ + --infile="${OUTFILE_CALL_UNSIGNED}" \ + --relayer-pem="${RELAYER_PEM}" \ + --proxy="${PROXY}" \ + --send \ + --outfile="${OUTFILE_CALL_RELAYED}" \ + --wait-result \ + || return +} + +# One-step variant: Alice signs and Bob co-signs in a single command. +# Requires both PEM files to be present on the same machine. +# The relayer address is derived automatically from --relayer-pem. +add_relayed_onestep() { + NUMBER=5 + + "${TX_TOOL[@]}" call "${ADDRESS}" \ + --pem="${PEM}" \ + --gas-limit=5050000 \ + --function="add" \ + --arguments "${NUMBER}" \ + --relayer-pem="${RELAYER_PEM}" \ + --proxy="${PROXY}" \ + --chain="${CHAIN}" \ + --send \ + --outfile="${OUTFILE_CALL}" \ + --wait-result \ + || return +} diff --git a/contracts/examples/bonding-curve-contract/Cargo.toml b/contracts/examples/bonding-curve-contract/Cargo.toml index a7a06f3628..b9e0490cc4 100644 --- a/contracts/examples/bonding-curve-contract/Cargo.toml +++ b/contracts/examples/bonding-curve-contract/Cargo.toml @@ -9,14 +9,11 @@ publish = false path = "src/bonding_curve_contract.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/bonding-curve-contract/meta/Cargo.toml b/contracts/examples/bonding-curve-contract/meta/Cargo.toml index 986ca51fcd..ebe0051fdc 100644 --- a/contracts/examples/bonding-curve-contract/meta/Cargo.toml +++ b/contracts/examples/bonding-curve-contract/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/check-pause/Cargo.toml b/contracts/examples/check-pause/Cargo.toml index 686a0437ad..2013a646b6 100644 --- a/contracts/examples/check-pause/Cargo.toml +++ b/contracts/examples/check-pause/Cargo.toml @@ -8,18 +8,12 @@ publish = false [lib] path = "src/check_pause.rs" -[dev-dependencies] -num-bigint = "0.4" - [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/check-pause/meta/Cargo.toml b/contracts/examples/check-pause/meta/Cargo.toml index 9abae403c7..cd7ae907bc 100644 --- a/contracts/examples/check-pause/meta/Cargo.toml +++ b/contracts/examples/check-pause/meta/Cargo.toml @@ -9,6 +9,4 @@ authors = ["Alin Cruceat "] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crowdfunding/Cargo.toml b/contracts/examples/crowdfunding/Cargo.toml index 370771e2f6..7292249d6b 100644 --- a/contracts/examples/crowdfunding/Cargo.toml +++ b/contracts/examples/crowdfunding/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/crowdfunding.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/examples/crowdfunding/meta/Cargo.toml b/contracts/examples/crowdfunding/meta/Cargo.toml index 1e05116420..4e28badf87 100644 --- a/contracts/examples/crowdfunding/meta/Cargo.toml +++ b/contracts/examples/crowdfunding/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-bubbles/Cargo.toml b/contracts/examples/crypto-bubbles/Cargo.toml index b904a147d3..d7aa840096 100644 --- a/contracts/examples/crypto-bubbles/Cargo.toml +++ b/contracts/examples/crypto-bubbles/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/crypto_bubbles.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/crypto-bubbles/meta/Cargo.toml b/contracts/examples/crypto-bubbles/meta/Cargo.toml index 5201719d8a..99068deb16 100644 --- a/contracts/examples/crypto-bubbles/meta/Cargo.toml +++ b/contracts/examples/crypto-bubbles/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-kitties/common/kitty/Cargo.toml b/contracts/examples/crypto-kitties/common/kitty/Cargo.toml index 81c110a70e..b80b374c90 100644 --- a/contracts/examples/crypto-kitties/common/kitty/Cargo.toml +++ b/contracts/examples/crypto-kitties/common/kitty/Cargo.toml @@ -9,8 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dependencies.random] version = "0.0.0" diff --git a/contracts/examples/crypto-kitties/common/random/Cargo.toml b/contracts/examples/crypto-kitties/common/random/Cargo.toml index ec6d5cb4ab..d0c11e25f3 100644 --- a/contracts/examples/crypto-kitties/common/random/Cargo.toml +++ b/contracts/examples/crypto-kitties/common/random/Cargo.toml @@ -8,5 +8,4 @@ edition = "2024" path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-auction/Cargo.toml b/contracts/examples/crypto-kitties/kitty-auction/Cargo.toml index 800b421835..a2f93ee5c3 100644 --- a/contracts/examples/crypto-kitties/kitty-auction/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-auction/Cargo.toml @@ -17,9 +17,7 @@ version = "0.0.0" path = "../kitty-ownership" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-auction/meta/Cargo.toml b/contracts/examples/crypto-kitties/kitty-auction/meta/Cargo.toml index e104da0f94..f1e63b0118 100644 --- a/contracts/examples/crypto-kitties/kitty-auction/meta/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-auction/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-genetic-alg/Cargo.toml b/contracts/examples/crypto-kitties/kitty-genetic-alg/Cargo.toml index a71cc91b77..f9c1f36e07 100644 --- a/contracts/examples/crypto-kitties/kitty-genetic-alg/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-genetic-alg/Cargo.toml @@ -18,9 +18,7 @@ version = "0.0.0" path = "../common/random" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-genetic-alg/meta/Cargo.toml b/contracts/examples/crypto-kitties/kitty-genetic-alg/meta/Cargo.toml index 64c2956ed7..16af2e0fc7 100644 --- a/contracts/examples/crypto-kitties/kitty-genetic-alg/meta/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-genetic-alg/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-ownership/Cargo.toml b/contracts/examples/crypto-kitties/kitty-ownership/Cargo.toml index 3ef0c62290..8dd681d59e 100644 --- a/contracts/examples/crypto-kitties/kitty-ownership/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-ownership/Cargo.toml @@ -21,9 +21,7 @@ version = "0.0.0" path = "../kitty-genetic-alg" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/examples/crypto-kitties/kitty-ownership/meta/Cargo.toml b/contracts/examples/crypto-kitties/kitty-ownership/meta/Cargo.toml index 5dacdb944e..9bb2fa9d89 100644 --- a/contracts/examples/crypto-kitties/kitty-ownership/meta/Cargo.toml +++ b/contracts/examples/crypto-kitties/kitty-ownership/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-zombies/Cargo.toml b/contracts/examples/crypto-zombies/Cargo.toml index 4043e85c10..9f2b29454b 100644 --- a/contracts/examples/crypto-zombies/Cargo.toml +++ b/contracts/examples/crypto-zombies/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/crypto-zombies/README.md b/contracts/examples/crypto-zombies/README.md new file mode 100644 index 0000000000..d19887860a --- /dev/null +++ b/contracts/examples/crypto-zombies/README.md @@ -0,0 +1,56 @@ +# Crypto Zombies Contract + +A MultiversX smart contract adapted from the [CryptoZombies](https://cryptozombies.io) tutorial. Demonstrates multi-module contract composition, NFT-style ownership, combat mechanics, and ESDT kitty feeding — making it a practical reference for contracts split across multiple trait modules. + +It is also available as an `sc-meta` template (see "Using as a template" below). + +## Contract modules + +| Module | Description | +|---|---| +| `ZombieFactory` | Create zombies with random DNA; one zombie per address | +| `ZombieFeeding` | Feed a zombie a CryptoKitty to level it up and mutate its DNA | +| `ZombieHelper` | Level-up fee collection; change zombie name/DNA at higher levels | +| `ZombieAttack` | Attack other zombies; win/loss tracking with cooldown | +| `Storage` | All storage mappers shared across modules | + +## Project structure + +``` +crypto-zombies/ +├── src/ +│ ├── lib.rs # Contract entry point +│ ├── zombie.rs # Zombie struct +│ ├── zombie_factory.rs +│ ├── zombie_feeding.rs +│ ├── zombie_helper.rs +│ ├── zombie_attack.rs +│ ├── storage.rs +│ ├── kitty_obj.rs # CryptoKitty struct (for ESDT callback) +│ ├── kitty_ownership_proxy.rs # Proxy for the kitty ownership contract +│ └── proxy.rs # Auto-generated proxy (sc-meta all proxy) +├── tests/ +│ └── crypto_zombies_blackbox_test.rs +├── meta/ # Build metadata +├── wasm/ # Wasm build output +├── Cargo.toml +└── multiversx.json +``` + +## Building + +```bash +sc-meta all build +``` + +## Testing + +```bash +cargo test +``` + +## Using as a template + +```bash +sc-meta new --template crypto-zombies --name my-contract +``` diff --git a/contracts/examples/crypto-zombies/meta/Cargo.toml b/contracts/examples/crypto-zombies/meta/Cargo.toml index a3657233fd..3ec61c5f30 100644 --- a/contracts/examples/crypto-zombies/meta/Cargo.toml +++ b/contracts/examples/crypto-zombies/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/crypto-zombies/mxsc-template.toml b/contracts/examples/crypto-zombies/mxsc-template.toml index 475743584c..e1ee56e910 100644 --- a/contracts/examples/crypto-zombies/mxsc-template.toml +++ b/contracts/examples/crypto-zombies/mxsc-template.toml @@ -9,5 +9,6 @@ files_include = [ "tests", "Cargo.toml", "multiversx.json", + "README.md", "wasm/Cargo.lock", ] diff --git a/contracts/examples/digital-cash/Cargo.toml b/contracts/examples/digital-cash/Cargo.toml index 9227a06488..3e374ff159 100644 --- a/contracts/examples/digital-cash/Cargo.toml +++ b/contracts/examples/digital-cash/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/digital_cash.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/digital-cash/meta/Cargo.toml b/contracts/examples/digital-cash/meta/Cargo.toml index a8b37fbaa2..2799c3a856 100644 --- a/contracts/examples/digital-cash/meta/Cargo.toml +++ b/contracts/examples/digital-cash/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/empty/Cargo.toml b/contracts/examples/empty/Cargo.toml index c4c32c07a2..233db5a206 100644 --- a/contracts/examples/empty/Cargo.toml +++ b/contracts/examples/empty/Cargo.toml @@ -9,12 +9,7 @@ publish = false path = "src/empty.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" - -[dev-dependencies] -num-bigint = "0.4" +workspace = true diff --git a/contracts/examples/empty/README.md b/contracts/examples/empty/README.md new file mode 100644 index 0000000000..f2b20f31ea --- /dev/null +++ b/contracts/examples/empty/README.md @@ -0,0 +1,47 @@ +# Empty Contract + +A minimal MultiversX smart contract with no logic. Intended as a **starting template** when creating a new contract from scratch. + +## Contract + +The contract defines a single trait `EmptyContract` with: + +- `#[init]` — empty initializer, called on deploy +- `#[upgrade]` — empty upgrade handler, called on contract upgrade + +## Project structure + +``` +empty/ +├── src/ +│ └── empty.rs # Contract source +├── tests/ +│ ├── empty_scenario_go_test.rs # Scenario tests (Go runner) +│ └── empty_scenario_rs_test.rs # Scenario tests (Rust runner) +├── scenarios/ +│ └── empty.scen.json # Scenario definition +├── meta/ # Build metadata +├── wasm/ # Wasm build output +├── Cargo.toml +└── multiversx.json +``` + +## Building + +```bash +sc-meta all build +``` + +## Testing + +```bash +cargo test +``` + +## Using as a template + +This contract is registered as an `sc-meta` template under the name `empty`. To scaffold a new project from it: + +```bash +sc-meta new --template empty --name my-contract +``` diff --git a/contracts/examples/empty/meta/Cargo.toml b/contracts/examples/empty/meta/Cargo.toml index c575a2ac7d..b27955b7f9 100644 --- a/contracts/examples/empty/meta/Cargo.toml +++ b/contracts/examples/empty/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/empty/mxsc-template.toml b/contracts/examples/empty/mxsc-template.toml index ca48a357fc..ec2af2ea1d 100644 --- a/contracts/examples/empty/mxsc-template.toml +++ b/contracts/examples/empty/mxsc-template.toml @@ -12,7 +12,8 @@ files_include = [ "scenarios", "src", "tests", + "wasm/Cargo.lock", "Cargo.toml", "multiversx.json", - "wasm/Cargo.lock", + "README.md", ] diff --git a/contracts/examples/esdt-transfer-with-fee/Cargo.toml b/contracts/examples/esdt-transfer-with-fee/Cargo.toml index 04601f1211..33ebdf9c56 100644 --- a/contracts/examples/esdt-transfer-with-fee/Cargo.toml +++ b/contracts/examples/esdt-transfer-with-fee/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/esdt_transfer_with_fee.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/esdt-transfer-with-fee/meta/Cargo.toml b/contracts/examples/esdt-transfer-with-fee/meta/Cargo.toml index bd776a294d..6b5449d22e 100644 --- a/contracts/examples/esdt-transfer-with-fee/meta/Cargo.toml +++ b/contracts/examples/esdt-transfer-with-fee/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/factorial/Cargo.toml b/contracts/examples/factorial/Cargo.toml index 572bd8015b..0807f54ae3 100644 --- a/contracts/examples/factorial/Cargo.toml +++ b/contracts/examples/factorial/Cargo.toml @@ -12,10 +12,8 @@ path = "src/factorial.rs" compiled-sc-tests = ["multiversx-sc-scenario/compiled-sc-tests"] [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/examples/factorial/meta/Cargo.toml b/contracts/examples/factorial/meta/Cargo.toml index 22c9c691da..14c8e03c1b 100644 --- a/contracts/examples/factorial/meta/Cargo.toml +++ b/contracts/examples/factorial/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/fractional-nfts/Cargo.toml b/contracts/examples/fractional-nfts/Cargo.toml index 494a76cc9a..fca8e485ec 100644 --- a/contracts/examples/fractional-nfts/Cargo.toml +++ b/contracts/examples/fractional-nfts/Cargo.toml @@ -9,13 +9,10 @@ publish = false path = "src/fractional_nfts.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/fractional-nfts/meta/Cargo.toml b/contracts/examples/fractional-nfts/meta/Cargo.toml index ecfa38697e..aa23d5eb22 100644 --- a/contracts/examples/fractional-nfts/meta/Cargo.toml +++ b/contracts/examples/fractional-nfts/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/greeter/.gitignore b/contracts/examples/greeter/.gitignore new file mode 100644 index 0000000000..eda1e811c3 --- /dev/null +++ b/contracts/examples/greeter/.gitignore @@ -0,0 +1,5 @@ +# Build artifacts — regenerate with `sc-meta all build`. +target/ +output/ + +# Keep Cargo.lock and wasm/Cargo.lock tracked (reproducible builds) diff --git a/contracts/examples/greeter/Cargo.toml b/contracts/examples/greeter/Cargo.toml new file mode 100644 index 0000000000..a2cbdbf7c1 --- /dev/null +++ b/contracts/examples/greeter/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "greeter" +version = "0.0.0" +edition = "2024" +publish = false +authors = ["you"] + +[lib] +path = "src/greeter.rs" + +[dependencies.multiversx-sc] +workspace = true + +[dev-dependencies.multiversx-sc-scenario] +workspace = true diff --git a/contracts/examples/greeter/README.md b/contracts/examples/greeter/README.md new file mode 100644 index 0000000000..45861ded81 --- /dev/null +++ b/contracts/examples/greeter/README.md @@ -0,0 +1,58 @@ +# Greeter Contract + +A minimal MultiversX smart contract that stores and retrieves a per-caller greeting message. Built on top of the `empty` template by adding one endpoint, one view, and one storage mapper — the smallest meaningful step beyond a bare scaffold. + +It is also available as an `sc-meta` template (see "Using as a template" below). + +## Contract + +The `Greeter` trait exposes: + +| Member | Type | Description | +|---|---|---| +| `init` | `#[init]` | Empty initializer, called on deploy | +| `upgrade` | `#[upgrade]` | Empty upgrade handler | +| `setGreeting(message)` | `#[endpoint]` | Stores a greeting for the calling address | +| `getGreeting(address)` | `#[view]` | Returns the greeting stored for `address`, or an empty buffer if none | + +Storage is keyed per address via a parameterized `SingleValueMapper`, so each caller has an independent greeting. + +## Project structure + +``` +greeter/ +├── src/ +│ ├── greeter.rs # Contract source +│ └── greeter_proxy.rs # Auto-generated proxy (sc-meta all proxy) +├── tests/ +│ └── greeter_blackbox_test.rs +├── meta/ # Build metadata +├── wasm/ # Wasm build output +├── sc-config.toml # Proxy output path +├── Cargo.toml +└── multiversx.json +``` + +## Building + +```bash +sc-meta all build +``` + +## Regenerating the proxy + +```bash +sc-meta all proxy +``` + +## Testing + +```bash +cargo test +``` + +## Using as a template + +```bash +sc-meta new --template greeter --name my-contract +``` diff --git a/contracts/examples/greeter/meta/Cargo.toml b/contracts/examples/greeter/meta/Cargo.toml new file mode 100644 index 0000000000..4ef4c0662d --- /dev/null +++ b/contracts/examples/greeter/meta/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "greeter-meta" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies.greeter] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +workspace = true diff --git a/contracts/examples/greeter/meta/src/main.rs b/contracts/examples/greeter/meta/src/main.rs new file mode 100644 index 0000000000..b76002a4db --- /dev/null +++ b/contracts/examples/greeter/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/contracts/examples/greeter/multiversx.json b/contracts/examples/greeter/multiversx.json new file mode 100644 index 0000000000..7365539625 --- /dev/null +++ b/contracts/examples/greeter/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/examples/greeter/mxsc-template.toml b/contracts/examples/greeter/mxsc-template.toml new file mode 100644 index 0000000000..d3164ea7cb --- /dev/null +++ b/contracts/examples/greeter/mxsc-template.toml @@ -0,0 +1,15 @@ +name = "greeter" +contract_trait = "Greeter" +src_file = "greeter.rs" +rename_pairs = [] +files_include = [ + "meta", + "scenarios", + "src", + "tests", + "wasm/Cargo.lock", + "Cargo.toml", + "multiversx.json", + "README.md", + "sc-config.toml", +] diff --git a/contracts/examples/greeter/sc-config.toml b/contracts/examples/greeter/sc-config.toml new file mode 100644 index 0000000000..7f61cffe27 --- /dev/null +++ b/contracts/examples/greeter/sc-config.toml @@ -0,0 +1,7 @@ +# Not generated by `sc-meta new --template empty` — confirmed empirically +# while authoring this recipe: a freshly scaffolded "empty" contract has +# no sc-config.toml at all. It's only needed once you want proxy +# generation (or a multi-contract build), so it's added here as the +# first real step past the bare scaffold. +[[proxy]] +path = "src/greeter_proxy.rs" diff --git a/contracts/examples/greeter/src/greeter.rs b/contracts/examples/greeter/src/greeter.rs new file mode 100644 index 0000000000..a4f6bece71 --- /dev/null +++ b/contracts/examples/greeter/src/greeter.rs @@ -0,0 +1,33 @@ +#![no_std] + +use multiversx_sc::imports::*; + +pub mod greeter_proxy; + +/// A minimal greeter contract. Starts from `sc-meta new --template empty +/// --name greeter` (see this recipe's README for the exact, unmodified +/// output of that command) with one endpoint and one storage mapper added +/// on top — the smallest real next step after scaffolding, and the +/// natural bridge into the storage-mapper-decision-table recipe. +#[multiversx_sc::contract] +pub trait Greeter { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + /// Stores a greeting message for the calling address. Overwrites any + /// previous greeting for the same caller. + #[endpoint(setGreeting)] + fn set_greeting(&self, message: ManagedBuffer) { + let caller = self.blockchain().get_caller(); + self.greeting(&caller).set(message); + } + + /// Reads back the greeting message stored for `address`. Returns an + /// empty ManagedBuffer if that address never called setGreeting. + #[view(getGreeting)] + #[storage_mapper("greeting")] + fn greeting(&self, address: &ManagedAddress) -> SingleValueMapper; +} diff --git a/contracts/examples/greeter/src/greeter_proxy.rs b/contracts/examples/greeter/src/greeter_proxy.rs new file mode 100644 index 0000000000..c3f1239a40 --- /dev/null +++ b/contracts/examples/greeter/src/greeter_proxy.rs @@ -0,0 +1,113 @@ +// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +#![allow(dead_code)] +#![allow(clippy::all)] + +use multiversx_sc::proxy_imports::*; + +pub struct GreeterProxy; + +impl TxProxyTrait for GreeterProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = GreeterProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + GreeterProxyMethods { wrapped_tx: tx } + } +} + +pub struct GreeterProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl GreeterProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init( + self, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .original_result() + } +} + +#[rustfmt::skip] +impl GreeterProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn upgrade( + self, + ) -> TxTypedUpgrade { + self.wrapped_tx + .payment(NotPayable) + .raw_upgrade() + .original_result() + } +} + +#[rustfmt::skip] +impl GreeterProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + /// Stores a greeting message for the calling address. Overwrites any + /// previous greeting for the same caller. + pub fn set_greeting< + Arg0: ProxyArg>, + >( + self, + message: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setGreeting") + .argument(&message) + .original_result() + } + + /// Reads back the greeting message stored for `address`. Returns an + /// empty ManagedBuffer if that address never called setGreeting. + pub fn greeting< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getGreeting") + .argument(&address) + .original_result() + } +} diff --git a/contracts/examples/greeter/tests/greeter_blackbox_test.rs b/contracts/examples/greeter/tests/greeter_blackbox_test.rs new file mode 100644 index 0000000000..9025eebc78 --- /dev/null +++ b/contracts/examples/greeter/tests/greeter_blackbox_test.rs @@ -0,0 +1,110 @@ +// tests/greeter_blackbox_test.rs — a hand-written blackbox test for the +// endpoint added on top of the bare `empty` scaffold. The two +// `greeter_scenario_*_test.rs` files (unmodified from `sc-meta new +// --template empty`) only exercise deploy; this test exercises the +// actual custom logic, following CLAUDE.md §"Blackbox Tests +// (RECOMMENDED)" pattern exactly. + +use multiversx_sc_scenario::imports::*; + +use greeter::greeter_proxy; + +const OWNER: TestAddress = TestAddress::new("owner"); +const CONTRACT: TestSCAddress = TestSCAddress::new("greeter-contract"); +const CODE_PATH: MxscPath = MxscPath::new("output/greeter.mxsc.json"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract(CODE_PATH, greeter::ContractBuilder); + blockchain +} + +#[test] +fn set_and_get_greeting() { + let mut world = world(); + world.account(OWNER).nonce(1); + + // Deploy. + world + .tx() + .from(OWNER) + .typed(greeter_proxy::GreeterProxy) + .init() + .code(CODE_PATH) + .new_address(CONTRACT) + .run(); + + // Before any call, the greeting for OWNER is empty. + world + .query() + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .greeting(OWNER.to_address()) + .returns(ExpectValue(ManagedBuffer::::new())) + .run(); + + // Call setGreeting as OWNER. + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .set_greeting(ManagedBuffer::::from(b"hello devnet")) + .run(); + + // getGreeting now returns what we stored, keyed by the caller address. + world + .query() + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .greeting(OWNER.to_address()) + .returns(ExpectValue(ManagedBuffer::::from( + b"hello devnet", + ))) + .run(); +} + +#[test] +fn greeting_is_keyed_per_caller() { + let mut world = world(); + world.account(OWNER).nonce(1); + let other: TestAddress = TestAddress::new("other-caller"); + world.account(other).nonce(1); + + world + .tx() + .from(OWNER) + .typed(greeter_proxy::GreeterProxy) + .init() + .code(CODE_PATH) + .new_address(CONTRACT) + .run(); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .set_greeting("from owner") + .run(); + + // A different caller who never called setGreeting still reads back + // empty — this is what CLAUDE.md §"Storage Mappers" means by "the key + // includes the parameter automatically": each address gets its own + // storage slot, not a shared one. + world + .query() + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .greeting(other.to_address()) + .returns(ExpectValue("")) + .run(); + + world + .query() + .to(CONTRACT) + .typed(greeter_proxy::GreeterProxy) + .greeting(OWNER.to_address()) + .returns(ExpectValue("from owner")) + .run(); +} diff --git a/contracts/examples/greeter/wasm/Cargo.lock b/contracts/examples/greeter/wasm/Cargo.lock new file mode 100644 index 0000000000..6e8955755d --- /dev/null +++ b/contracts/examples/greeter/wasm/Cargo.lock @@ -0,0 +1,205 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "endian-type" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" + +[[package]] +name = "generic-array" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" +dependencies = [ + "rustversion", + "typenum", +] + +[[package]] +name = "greeter" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "greeter-wasm" +version = "0.0.0" +dependencies = [ + "greeter", + "multiversx-sc-wasm-adapter", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "multiversx-chain-core" +version = "0.23.1" +dependencies = [ + "bitflags", + "multiversx-sc-codec", +] + +[[package]] +name = "multiversx-sc" +version = "0.66.2" +dependencies = [ + "bitflags", + "generic-array", + "multiversx-chain-core", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.25.0" +dependencies = [ + "arrayvec", + "bitflags", + "multiversx-sc-codec-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.25.0" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.66.2" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.66.2" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unwrap-infallible" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e497bb1f828cc9fb236722c2eaa100dcf201563f38f4da6252357a59037adf31" diff --git a/contracts/examples/greeter/wasm/Cargo.toml b/contracts/examples/greeter/wasm/Cargo.toml new file mode 100644 index 0000000000..eec3775f63 --- /dev/null +++ b/contracts/examples/greeter/wasm/Cargo.toml @@ -0,0 +1,35 @@ +# Code generated by the multiversx-sc build system. DO NOT EDIT. + +# ########################################## +# ############## AUTO-GENERATED ############# +# ########################################## + +[package] +name = "greeter-wasm" +version = "0.0.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" +overflow-checks = false + +[profile.dev] +panic = "abort" + +[dependencies.greeter] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.66.2" +path = "../../../../framework/wasm-adapter" + +[workspace] +members = ["."] diff --git a/contracts/examples/greeter/wasm/src/lib.rs b/contracts/examples/greeter/wasm/src/lib.rs new file mode 100644 index 0000000000..ab3c2a2a7b --- /dev/null +++ b/contracts/examples/greeter/wasm/src/lib.rs @@ -0,0 +1,28 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 2 +// Async Callback (empty): 1 +// Total number of exported functions: 5 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + greeter + ( + init => init + upgrade => upgrade + setGreeting => set_greeting + getGreeting => greeting + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/contracts/examples/lottery/Cargo.toml b/contracts/examples/lottery/Cargo.toml index cd3d42a65c..075632ba06 100644 --- a/contracts/examples/lottery/Cargo.toml +++ b/contracts/examples/lottery/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/lottery.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/lottery/meta/Cargo.toml b/contracts/examples/lottery/meta/Cargo.toml index 7b9dd21956..a48e579ef4 100644 --- a/contracts/examples/lottery/meta/Cargo.toml +++ b/contracts/examples/lottery/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/multisig/Cargo.toml b/contracts/examples/multisig/Cargo.toml index f775495e8c..dc858fe6ed 100644 --- a/contracts/examples/multisig/Cargo.toml +++ b/contracts/examples/multisig/Cargo.toml @@ -9,14 +9,11 @@ publish = false path = "src/multisig.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/examples/multisig/interact/Cargo.toml b/contracts/examples/multisig/interact/Cargo.toml index 24aabcf9fe..6b594d49a3 100644 --- a/contracts/examples/multisig/interact/Cargo.toml +++ b/contracts/examples/multisig/interact/Cargo.toml @@ -19,17 +19,13 @@ tokio = { version = "1.24" } path = ".." [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../../contracts/modules" +workspace = true [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies.multiversx-sc-scenario] -version = "=0.66.2" -path = "../../../../framework/scenario" +workspace = true [dependencies.multiversx-sc] -version = "=0.66.2" -path = "../../../../framework/base" +workspace = true diff --git a/contracts/examples/multisig/interact/config.toml b/contracts/examples/multisig/interact/config.toml index 148ebb5803..ff2c5cc918 100644 --- a/contracts/examples/multisig/interact/config.toml +++ b/contracts/examples/multisig/interact/config.toml @@ -1,4 +1,23 @@ +[connection] chain_type = 'real' -gateway_uri = 'https://devnet-gateway.multiversx.com' +gateway_uri = 'https://testnet-gateway.multiversx.com' + +[general] +contract_path = '../output/multisig.mxsc.json' quorum = 2 wegld_address = "erd1qqqqqqqqqqqqqpgqqkwzsxkjc83vlfex9dmznwm7tjvxlqqkpauqx0n782" + +[wallet] +test_wallet = 'mike' + +[[board]] +test_wallet = 'mike' + +[[board]] +test_wallet = 'carol' + +[[board]] +test_wallet = 'dan' + +[[board]] +test_wallet = 'eve' diff --git a/contracts/examples/multisig/interact/src/multisig_interact.rs b/contracts/examples/multisig/interact/src/multisig_interact.rs index 59574edd76..1c07dd67c9 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact.rs @@ -19,7 +19,6 @@ async fn main() { env_logger::init(); let mut multisig_interact = MultisigInteract::init().await; - multisig_interact.register_wallets().await; let cli = multisig_interact_cli::InteractCli::parse(); match &cli.command { @@ -76,7 +75,6 @@ async fn main() { struct MultisigInteract { interactor: Interactor, - wallet_address: Bech32Address, collection_token_identifier: String, multisig_code: BytesValue, config: Config, @@ -85,22 +83,16 @@ struct MultisigInteract { impl MultisigInteract { async fn init() -> Self { - let config = Config::load_config(); - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()) - .with_tracer(INTERACTOR_SCENARIO_TRACE_PATH) - .await; - interactor.set_current_dir_from_workspace("contracts/examples/multisig/interact"); - let wallet_address = interactor.register_wallet(test_wallets::mike()).await; + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let interactor = interactor.with_tracer(INTERACTOR_SCENARIO_TRACE_PATH).await; let multisig_code = BytesValue::interpret_from( - "mxsc:../output/multisig.mxsc.json", + format!("mxsc:{}", config.general.contract_path), &InterpreterContext::default(), ); Self { interactor, - wallet_address: wallet_address.into(), collection_token_identifier: String::new(), multisig_code, config, @@ -108,16 +100,6 @@ impl MultisigInteract { } } - async fn register_wallets(&mut self) { - let carol = test_wallets::carol(); - let dan = test_wallets::dan(); - let eve = test_wallets::eve(); - - for wallet in &[carol, dan, eve] { - self.interactor.register_wallet(*wallet).await; - } - } - async fn set_state(&mut self) { for board_member_address in self.board().iter() { println!( @@ -137,15 +119,16 @@ impl MultisigInteract { let board = self.board(); - let quorum = self.config.quorum; + let quorum = self.config.general.quorum; let new_address = self .interactor .tx() .id("deploy multisig") - .from(&self.wallet_address) + .from(self.config.wallet.address()) .typed(multisig_proxy::MultisigProxy) .init(quorum, board) .code(&self.multisig_code) + .code_metadata(CodeMetadata::PAYABLE) .gas(NumExpr("100,000,000")) .returns(ReturnsNewBech32Address) .run() @@ -165,11 +148,11 @@ impl MultisigInteract { println!("deploying {count} contracts..."); let board = self.board(); - let quorum = Config::load_config().quorum; + let quorum = self.config.general.quorum; let mut buffer = self.interactor.homogenous_call_buffer(); for _ in 0..*count { buffer.push_tx(|tx| { - tx.from(&self.wallet_address) + tx.from(self.config.wallet.address()) .typed(multisig_proxy::MultisigProxy) .init(quorum, board.clone()) .code(&self.multisig_code) @@ -185,25 +168,23 @@ impl MultisigInteract { } } - fn board(&mut self) -> MultiValueVec
{ - let carol = test_wallets::carol(); - let dan = test_wallets::dan(); - let eve = test_wallets::eve(); - - MultiValueVec::from([ - self.wallet_address.to_address(), - carol.to_address(), - dan.to_address(), - eve.to_address(), - ]) + fn board(&self) -> MultiValueVec
{ + MultiValueVec::from( + self.config + .board + .iter() + .map(|w| w.wallet().to_address()) + .collect::>(), + ) } async fn feed_contract_egld(&mut self) { self.interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) - .egld(BigUint::from(50_000_000_000_000_000u64)) // 0,05 or 5 * 10^16 + .egld(50_000_000_000_000_000u64) // 0,05 or 5 * 10^16 + .gas(50_000) .run() .await; } @@ -216,7 +197,7 @@ impl MultisigInteract { self.interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(gas_expr) .typed(multisig_proxy::MultisigProxy) @@ -239,12 +220,11 @@ impl MultisigInteract { self.sign(&actions_no_quorum_reached).await; - let from = &self.wallet_address; let mut buffer = self.interactor.homogenous_call_buffer(); let multisig_address = self.state.current_multisig_address(); for action_id in action_ids { buffer.push_tx(|tx| { - tx.from(from) + tx.from(self.config.wallet.address()) .to(multisig_address) .gas(gas_expr) .typed(multisig_proxy::MultisigProxy) @@ -334,7 +314,7 @@ impl MultisigInteract { let dns_address = dns_address_for_name(name); self.interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("30,000,000")) .typed(multisig_proxy::MultisigProxy) diff --git a/contracts/examples/multisig/interact/src/multisig_interact_config.rs b/contracts/examples/multisig/interact/src/multisig_interact_config.rs index c11c2c447f..37e6ce3f58 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact_config.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact_config.rs @@ -1,45 +1,29 @@ -use multiversx_sc_scenario::imports::Bech32Address; +use multiversx_sc_snippets::imports::*; use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; +/// General settings for the multisig interact. #[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, +pub struct GeneralConfig { + pub contract_path: ConfigPath, + pub quorum: usize, + pub wegld_address: Bech32Address, } /// Multisig Interact configuration #[derive(Debug, Deserialize)] pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, - pub quorum: usize, - pub wegld_address: Bech32Address, + pub connection: ConnectionConfig, + pub general: GeneralConfig, + pub wallet: WalletConfig, + pub board: Vec, } -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection } - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } + fn register_wallets(&self) -> Vec { + self.board.iter().map(|w| w.wallet().clone()).collect() } } diff --git a/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs b/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs index 50300238f3..2f78217ca9 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs @@ -36,7 +36,7 @@ impl MultisigInteract { let action_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("10,000,000")) .typed(multisig_proxy::MultisigProxy) @@ -68,7 +68,7 @@ impl MultisigInteract { let new_token_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("80,000,000")) .typed(multisig_proxy::MultisigProxy) @@ -88,7 +88,7 @@ impl MultisigInteract { let action_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("10,000,000")) .typed(multisig_proxy::MultisigProxy) @@ -118,7 +118,7 @@ impl MultisigInteract { let new_token_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("80,000,000")) .typed(multisig_proxy::MultisigProxy) @@ -139,7 +139,7 @@ impl MultisigInteract { let action_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("10,000,000")) .typed(multisig_proxy::MultisigProxy) @@ -179,7 +179,7 @@ impl MultisigInteract { ); buffer.push_tx(|tx| { - tx.from(&self.wallet_address) + tx.from(self.config.wallet.address()) .to(multisig_address) .gas(10_000_000u64) .typed(multisig_proxy::MultisigProxy) diff --git a/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs b/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs index 08fc637f6a..59027790f8 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs @@ -34,7 +34,7 @@ impl MultisigInteract { pub async fn wegld_swap_set_state(&mut self) { self.interactor - .retrieve_account(&self.config.wegld_address) + .retrieve_account(&self.config.general.wegld_address) .await; } @@ -42,7 +42,7 @@ impl MultisigInteract { let function_call = self .interactor .tx() - .to(&self.config.wegld_address) + .to(&self.config.general.wegld_address) .typed(wegld_proxy::EgldEsdtSwapProxy) .wrap_egld() .into_function_call(); @@ -50,11 +50,15 @@ impl MultisigInteract { let action_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("10,000,000")) .typed(multisig_proxy::MultisigProxy) - .propose_async_call(&self.config.wegld_address, WRAP_AMOUNT, function_call) + .propose_async_call( + &self.config.general.wegld_address, + WRAP_AMOUNT, + function_call, + ) .returns(ReturnsResult) .run() .await; @@ -67,7 +71,7 @@ impl MultisigInteract { let wegld_token_id = self .interactor .query() - .to(&self.config.wegld_address) + .to(&self.config.general.wegld_address) .typed(wegld_proxy::EgldEsdtSwapProxy) .wrapped_egld_token_id() .returns(ReturnsResult) @@ -85,7 +89,7 @@ impl MultisigInteract { let normalized_tx = self .interactor .tx() - .to(&self.config.wegld_address) + .to(&self.config.general.wegld_address) .typed(wegld_proxy::EgldEsdtSwapProxy) .unwrap_egld() .single_esdt(&wegld_token_id, 0u64, &UNWRAP_AMOUNT.into()) @@ -96,7 +100,7 @@ impl MultisigInteract { let action_id = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_multisig_address()) .gas(NumExpr("10,000,000")) .typed(multisig_proxy::MultisigProxy) diff --git a/contracts/examples/multisig/meta/Cargo.toml b/contracts/examples/multisig/meta/Cargo.toml index 28b433de40..7ba0f0e268 100644 --- a/contracts/examples/multisig/meta/Cargo.toml +++ b/contracts/examples/multisig/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/multisig/tests/multisig_blackbox_test.rs b/contracts/examples/multisig/tests/multisig_blackbox_test.rs index bc2579ba4f..060cff6d7e 100644 --- a/contracts/examples/multisig/tests/multisig_blackbox_test.rs +++ b/contracts/examples/multisig/tests/multisig_blackbox_test.rs @@ -530,7 +530,7 @@ fn blackbox_deploy_and_upgrade_from_source() { let action_id = state.propose_sc_deploy_from_source( 0u64, ADDER_ADDRESS, - CodeMetadata::all(), + CodeMetadata::default(), MultiValueVec::from([top_encode_to_vec_u8_or_panic(&5u64)]), ); state.sign(action_id); @@ -576,7 +576,7 @@ fn blackbox_deploy_and_upgrade_from_source() { ADDER_ADDRESS, 0u64, factorial_address, - CodeMetadata::all(), + CodeMetadata::default(), MultiValueVec::new(), ); state.sign(action_id); diff --git a/contracts/examples/multisig/tests/multisig_whitebox_test.rs b/contracts/examples/multisig/tests/multisig_whitebox_test.rs index f602753aa6..f57781b767 100644 --- a/contracts/examples/multisig/tests/multisig_whitebox_test.rs +++ b/contracts/examples/multisig/tests/multisig_whitebox_test.rs @@ -626,7 +626,7 @@ fn whitebox_deploy_and_upgrade_from_source() { ActionRaw::SCDeployFromSource { amount: 0u64.into(), source: ADDER_ADDRESS.to_address(), - code_metadata: CodeMetadata::all(), + code_metadata: CodeMetadata::default(), arguments: vec![BoxedBytes::from(&[5u8][..])], }, None, @@ -683,7 +683,7 @@ fn whitebox_deploy_and_upgrade_from_source() { ActionRaw::SCUpgradeFromSource { source: FACTORIAL_ADDRESS.to_address(), amount: 0u64.into(), - code_metadata: CodeMetadata::all(), + code_metadata: CodeMetadata::default(), arguments: Vec::new(), sc_address: ADDER_ADDRESS.to_address(), }, diff --git a/contracts/examples/nft-minter/Cargo.toml b/contracts/examples/nft-minter/Cargo.toml index 5d85959d47..0d78c3d6ec 100644 --- a/contracts/examples/nft-minter/Cargo.toml +++ b/contracts/examples/nft-minter/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/nft-minter/meta/Cargo.toml b/contracts/examples/nft-minter/meta/Cargo.toml index 07a366e95a..01aaebeb8c 100644 --- a/contracts/examples/nft-minter/meta/Cargo.toml +++ b/contracts/examples/nft-minter/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/nft-storage-prepay/Cargo.toml b/contracts/examples/nft-storage-prepay/Cargo.toml index 60dfea0c12..96c231b44c 100644 --- a/contracts/examples/nft-storage-prepay/Cargo.toml +++ b/contracts/examples/nft-storage-prepay/Cargo.toml @@ -10,9 +10,7 @@ path = "src/nft_storage_prepay.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/nft-storage-prepay/meta/Cargo.toml b/contracts/examples/nft-storage-prepay/meta/Cargo.toml index 076aee3c50..ecafb25157 100644 --- a/contracts/examples/nft-storage-prepay/meta/Cargo.toml +++ b/contracts/examples/nft-storage-prepay/meta/Cargo.toml @@ -11,6 +11,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/nft-subscription/Cargo.toml b/contracts/examples/nft-subscription/Cargo.toml index 86cd1a36c2..d5fc750326 100644 --- a/contracts/examples/nft-subscription/Cargo.toml +++ b/contracts/examples/nft-subscription/Cargo.toml @@ -9,13 +9,10 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/nft-subscription/meta/Cargo.toml b/contracts/examples/nft-subscription/meta/Cargo.toml index 99cc8a9505..603253d4f4 100644 --- a/contracts/examples/nft-subscription/meta/Cargo.toml +++ b/contracts/examples/nft-subscription/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/order-book/factory/Cargo.toml b/contracts/examples/order-book/factory/Cargo.toml index 0ddde8253d..60becf2c6e 100644 --- a/contracts/examples/order-book/factory/Cargo.toml +++ b/contracts/examples/order-book/factory/Cargo.toml @@ -8,10 +8,8 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/examples/order-book/factory/meta/Cargo.toml b/contracts/examples/order-book/factory/meta/Cargo.toml index 000a91d3d9..0ff9635110 100644 --- a/contracts/examples/order-book/factory/meta/Cargo.toml +++ b/contracts/examples/order-book/factory/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/order-book/factory/src/lib.rs b/contracts/examples/order-book/factory/src/lib.rs index d6ca7e9642..1df8c7115a 100644 --- a/contracts/examples/order-book/factory/src/lib.rs +++ b/contracts/examples/order-book/factory/src/lib.rs @@ -33,7 +33,7 @@ pub trait Factory { .raw_deploy() .arguments_raw(arguments) .from_source(source) - .code_metadata(CodeMetadata::DEFAULT) + .code_metadata(CodeMetadata::EMPTY) .returns(ReturnsNewManagedAddress) .sync_call(); diff --git a/contracts/examples/order-book/pair/Cargo.toml b/contracts/examples/order-book/pair/Cargo.toml index ae9835c009..508aec9412 100644 --- a/contracts/examples/order-book/pair/Cargo.toml +++ b/contracts/examples/order-book/pair/Cargo.toml @@ -8,9 +8,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/examples/order-book/pair/meta/Cargo.toml b/contracts/examples/order-book/pair/meta/Cargo.toml index dd0c3db12d..b3fd6b3528 100644 --- a/contracts/examples/order-book/pair/meta/Cargo.toml +++ b/contracts/examples/order-book/pair/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/ping-pong-egld/Cargo.toml b/contracts/examples/ping-pong-egld/Cargo.toml index c79390af74..01d06dd5f2 100644 --- a/contracts/examples/ping-pong-egld/Cargo.toml +++ b/contracts/examples/ping-pong-egld/Cargo.toml @@ -9,11 +9,9 @@ publish = false path = "src/ping_pong.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/examples/ping-pong-egld/README.md b/contracts/examples/ping-pong-egld/README.md index 7fadb4a01f..0691304330 100644 --- a/contracts/examples/ping-pong-egld/README.md +++ b/contracts/examples/ping-pong-egld/README.md @@ -1,3 +1,76 @@ -# PingPong +# Ping-Pong EGLD Contract -`PingPong` is a simple Smart Contract. +A MultiversX smart contract that accepts a fixed EGLD amount from users ("ping"), locks it for a configurable duration, then allows users to reclaim their funds ("pong") after the deadline passes. + +Key design points: +- Each address can ping **exactly once**. +- Only the exact `ping_amount` set at deploy is accepted — no more, no less. +- An optional funding cap prevents the contract from accepting more than `max_funds` total. +- An optional `activation_timestamp` delays when pinging opens. +- `pongAll` sends funds back to all registered users in one call, with gas-interrupt-and-resume support for large user sets. + +It is also available as an `sc-meta` template (see "Using as a template" below). + +## Endpoints and views + +| Name | Type | Description | +|---|---|---| +| `init(ping_amount, duration, opt_activation_timestamp, max_funds)` | `#[init]` | Configure the contract on deploy | +| `upgrade(...)` | `#[upgrade]` | Re-configure with the same parameters as `init` | +| `ping` | `#[payable("EGLD")] #[endpoint]` | Lock the exact `ping_amount`; callable once per address while the contract is active | +| `pong` | `#[endpoint]` | Reclaim your `ping_amount` after the deadline | +| `pongAll` | `#[endpoint]` | Send back funds to every registered user; returns `completed` or `interrupted` if gas runs low (resumable) | +| `getUserAddresses` | `#[view]` | List all addresses that pinged, in order | +| `getContractState` | `#[view]` | Snapshot of all configuration and progress fields | +| `getPingAmount` | `#[view]` | The required ping payment | +| `getDeadline` | `#[view]` | Timestamp after which pong is allowed | +| `getActivationTimestamp` | `#[view]` | Timestamp from which ping is allowed | +| `getMaxFunds` | `#[view]` | Optional total funding cap | +| `getUserStatus(user_id)` | `#[view]` | Per-user state: `New` / `Registered` / `Withdrawn` | +| `pongAllLastUser` | `#[view]` | Last user index processed by `pongAll` (0 = idle or complete) | + +## Project structure + +``` +ping-pong-egld/ +├── src/ +│ ├── ping_pong.rs # Contract logic +│ ├── proxy.rs # Auto-generated proxy (sc-meta all proxy) +│ └── types.rs # ContractState, UserStatus types +├── tests/ +│ ├── ping_pong_egld_blackbox_from_scenarios.rs +│ ├── ping_pong_egld_scenario_go_test.rs +│ └── ping_pong_egld_scenario_rs_test.rs +├── scenarios/ # JSON scenario files +├── interactor/ # Interactor (live network) tool +├── snippets/ # Shell snippets +├── meta/ # Build metadata +├── wasm/ # Wasm build output +├── sc-config.toml # Proxy output path +├── Cargo.toml +└── multiversx.json +``` + +## Building + +```bash +sc-meta all build +``` + +## Regenerating the proxy + +```bash +sc-meta all proxy +``` + +## Testing + +```bash +cargo test +``` + +## Using as a template + +```bash +sc-meta new --template ping-pong-egld --name my-contract +``` diff --git a/contracts/examples/ping-pong-egld/dapp/src/interactor.rs b/contracts/examples/ping-pong-egld/dapp/src/interactor.rs index 843540649e..d48305e899 100644 --- a/contracts/examples/ping-pong-egld/dapp/src/interactor.rs +++ b/contracts/examples/ping-pong-egld/dapp/src/interactor.rs @@ -42,8 +42,9 @@ pub struct ContractInteract { impl ContractInteract { pub async fn new() -> Self { let config = Config::new(); - let mut interactor = DappInteractor::new(&config.gateway).await; - interactor.set_current_dir_from_workspace("contracts/examples/ping-pong-egld/dapp"); + let mut interactor = DappInteractor::new(&config.gateway) + .await + .with_current_dir(env!("CARGO_MANIFEST_DIR")); let wallet_address = interactor.register_wallet(test_wallets::mike()).await; let contract_code = BytesValue::from(PING_PONG_CODE); diff --git a/contracts/examples/ping-pong-egld/dapp/src/requests/query.rs b/contracts/examples/ping-pong-egld/dapp/src/requests/query.rs index 43bf114684..370222fdf5 100644 --- a/contracts/examples/ping-pong-egld/dapp/src/requests/query.rs +++ b/contracts/examples/ping-pong-egld/dapp/src/requests/query.rs @@ -10,7 +10,7 @@ pub async fn get_network_status() -> Result { let response = contract_interact .interactor - .proxy + .proxy() .request(NetworkStatusRequest::new(shard)) .await; diff --git a/contracts/examples/ping-pong-egld/interactor/Cargo.toml b/contracts/examples/ping-pong-egld/interactor/Cargo.toml index 93d08cfaec..7775830113 100644 --- a/contracts/examples/ping-pong-egld/interactor/Cargo.toml +++ b/contracts/examples/ping-pong-egld/interactor/Cargo.toml @@ -25,8 +25,7 @@ serial_test = "3.2" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [features] chain-simulator-tests = [] diff --git a/contracts/examples/ping-pong-egld/interactor/config.toml b/contracts/examples/ping-pong-egld/interactor/config.toml index aca748706a..984e2a13db 100644 --- a/contracts/examples/ping-pong-egld/interactor/config.toml +++ b/contracts/examples/ping-pong-egld/interactor/config.toml @@ -1,5 +1,13 @@ +[connection] chain_type = 'simulator' gateway_uri = 'http://localhost:8085' +# [connection] # chain_type = 'real' # gateway_uri = 'https://devnet-gateway.multiversx.com' + +[owner] +test_wallet = 'eve' + +[wallet] +test_wallet = 'mallory' diff --git a/contracts/examples/ping-pong-egld/interactor/src/interact.rs b/contracts/examples/ping-pong-egld/interactor/src/interact.rs index 1f7e5c6ab5..1660e66269 100644 --- a/contracts/examples/ping-pong-egld/interactor/src/interact.rs +++ b/contracts/examples/ping-pong-egld/interactor/src/interact.rs @@ -1,24 +1,51 @@ mod interact_cli; -mod interact_config; -mod interact_state; -use crate::interact_state::State; use clap::Parser; -pub use interact_config::Config; +use multiversx_sc_snippets::imports::*; use ping_pong_egld::proxy::{self, ContractState, UserStatus}; +use serde::{Deserialize, Serialize}; + +/// Ping Pong Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub connection: ConnectionConfig, + pub owner: WalletConfig, + pub wallet: WalletConfig, +} -use multiversx_sc_snippets::imports::*; +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + vec![self.owner.wallet().clone(), self.wallet.wallet().clone()] + } +} + +/// Ping Pong Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + pub ping_pong_egld_address: Option, +} + +impl State { + /// Returns the ping pong contract + pub fn current_ping_pong_egld_address(&self) -> &Bech32Address { + self.ping_pong_egld_address + .as_ref() + .expect("no known ping pong contract, deploy first") + } +} -const INTERACTOR_SCENARIO_TRACE_PATH: &str = "interactor_trace.scen.json"; +pub const INTERACTOR_SCENARIO_TRACE_PATH: &str = "interactor_trace.scen.json"; const PING_PONG_CODE: MxscPath = MxscPath::new("../output/ping-pong-egld.mxsc.json"); pub async fn ping_pong_egld_cli() { env_logger::init(); - let config = Config::load_config(); - - let mut interact = PingPongEgldInteract::init(config).await; + let mut interact = PingPongEgldInteract::new().await; let cli = interact_cli::InteractCli::parse(); match &cli.command { @@ -43,18 +70,25 @@ pub async fn ping_pong_egld_cli() { .await } Some(interact_cli::InteractCliCommand::Ping(args)) => { - let sender = interact.ping_pong_owner_address.clone(); - interact - .ping(args.cost.unwrap_or_default(), None, &sender) - .await + let sender = interact.config.owner.address(); + match interact.ping(&sender, args.cost.unwrap_or_default()).await { + Ok(_) => println!("Ping successful!"), + Err(err) => println!("Ping failed with message: {}", err.message), + } } Some(interact_cli::InteractCliCommand::Pong) => { - let sender = interact.ping_pong_owner_address.clone(); - interact.pong(None, &sender).await; + let sender = interact.config.owner.address(); + match interact.pong(&sender).await { + Ok(_) => println!("Pong successful!"), + Err(err) => println!("Pong failed with message: {}", err.message), + } } Some(interact_cli::InteractCliCommand::PongAll) => { - let sender = interact.ping_pong_owner_address.clone(); - interact.pong_all(None, &sender).await; + let sender = interact.config.owner.address(); + match interact.pong_all(&sender).await { + Ok(_) => println!("Pong All successful!"), + Err(err) => println!("Pong All failed with message: {}", err.message), + } } Some(interact_cli::InteractCliCommand::GetUserAddresses) => { let user_addresses = interact.get_user_addresses().await; @@ -111,40 +145,31 @@ pub async fn ping_pong_egld_cli() { pub struct PingPongEgldInteract { pub interactor: Interactor, - pub ping_pong_owner_address: Bech32Address, - pub wallet_address: Bech32Address, - pub state: State, + pub config: Config, + pub state: AutoSave, } impl PingPongEgldInteract { - pub async fn init(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()) + pub async fn new() -> Self { + let mut interactor = Interactor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) .with_tracer(INTERACTOR_SCENARIO_TRACE_PATH) .await; - - interactor.set_current_dir_from_workspace("contracts/examples/ping-pong-egld/interactor"); - let ping_pong_owner_address = interactor.register_wallet(test_wallets::eve()).await; - let wallet_address = interactor.register_wallet(test_wallets::mallory()).await; - - // generate blocks until ESDTSystemSCAddress is enabled - interactor.generate_blocks_until_all_activations().await; - + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); Self { interactor, - ping_pong_owner_address: ping_pong_owner_address.into(), - wallet_address: wallet_address.into(), - state: State::load_state(), + config, + state, } } pub async fn set_state(&mut self) { - println!("wallet address: {}", self.wallet_address); - self.interactor - .retrieve_account(&self.ping_pong_owner_address) - .await; - self.interactor.retrieve_account(&self.wallet_address).await; + let owner_address = self.config.owner.address(); + let wallet_address = self.config.wallet.address(); + println!("wallet address: {}", wallet_address); + self.interactor.retrieve_account(&owner_address).await; + self.interactor.retrieve_account(&wallet_address).await; } pub async fn deploy( @@ -156,10 +181,11 @@ impl PingPongEgldInteract { ) -> (u64, String) { self.set_state().await; + let owner_address = self.config.owner.address(); let (new_address, status, message) = self .interactor .tx() - .from(&self.ping_pong_owner_address) + .from(&owner_address) .gas(30_000_000u64) .typed(proxy::PingPongEgldProxy) .init(ping_amount, duration, opt_activation_timestamp, max_funds) @@ -171,7 +197,7 @@ impl PingPongEgldInteract { .await; println!("new address: {new_address}"); - self.state.set_ping_pong_egld_address(new_address); + self.state.ping_pong_egld_address = Some(new_address); (status, message) } @@ -187,7 +213,7 @@ impl PingPongEgldInteract { .interactor .tx() .to(self.state.current_ping_pong_egld_address()) - .from(&self.wallet_address) + .from(self.config.wallet.address()) .gas(30_000_000u64) .typed(proxy::PingPongEgldProxy) .upgrade(ping_amount, duration, opt_activation_timestamp, max_funds) @@ -199,7 +225,11 @@ impl PingPongEgldInteract { println!("Result: {response:?}"); } - pub async fn ping(&mut self, egld_amount: u64, message: Option<&str>, sender: &Bech32Address) { + pub async fn ping( + &mut self, + sender: &Bech32Address, + egld_amount: u64, + ) -> Result<(), TxResponseStatus> { let _data: IgnoreValue = IgnoreValue; let response = self @@ -215,16 +245,15 @@ impl PingPongEgldInteract { .run() .await; - match response { + match &response { Ok(_) => println!("Ping successful!"), - Err(err) => { - println!("Ping failed with message: {}", err.message); - assert_eq!(message.unwrap_or_default(), err.message); - } + Err(err) => println!("Ping failed with message: {}", err.message), } + + response.map(|_| ()) } - pub async fn pong(&mut self, message: Option<&str>, sender: &Bech32Address) { + pub async fn pong(&mut self, sender: &Bech32Address) -> Result<(), TxResponseStatus> { let response = self .interactor .tx() @@ -237,16 +266,15 @@ impl PingPongEgldInteract { .run() .await; - match response { + match &response { Ok(_) => println!("Pong successful!"), - Err(err) => { - println!("Pong failed with message: {}", err.message); - assert_eq!(message.unwrap_or_default(), err.message); - } + Err(err) => println!("Pong failed with message: {}", err.message), } + + response.map(|_| ()) } - pub async fn pong_all(&mut self, message: Option, sender: &Bech32Address) { + pub async fn pong_all(&mut self, sender: &Bech32Address) -> Result<(), TxResponseStatus> { let response = self .interactor .tx() @@ -259,13 +287,12 @@ impl PingPongEgldInteract { .run() .await; - match response { + match &response { Ok(_) => println!("Pong All successful!"), - Err(err) => { - println!("Pong All failed with message: {}", err.message); - assert_eq!(message.unwrap_or_default(), err.message); - } + Err(err) => println!("Pong All failed with message: {}", err.message), } + + response.map(|_| ()) } pub async fn get_user_addresses(&mut self) -> Vec { diff --git a/contracts/examples/ping-pong-egld/interactor/src/interact_config.rs b/contracts/examples/ping-pong-egld/interactor/src/interact_config.rs deleted file mode 100644 index 013e16714f..0000000000 --- a/contracts/examples/ping-pong-egld/interactor/src/interact_config.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Ping Pong Interact configuration -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/examples/ping-pong-egld/interactor/src/interact_state.rs b/contracts/examples/ping-pong-egld/interactor/src/interact_state.rs deleted file mode 100644 index 38fb3d320c..0000000000 --- a/contracts/examples/ping-pong-egld/interactor/src/interact_state.rs +++ /dev/null @@ -1,50 +0,0 @@ -use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; - -/// Multisig Interact state -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - ping_pong_egld_address: Option, -} - -impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - /// Sets the ping pong address - pub fn set_ping_pong_egld_address(&mut self, address: Bech32Address) { - self.ping_pong_egld_address = Some(address); - } - - /// Returns the ping pong contract - pub fn current_ping_pong_egld_address(&self) -> &Bech32Address { - self.ping_pong_egld_address - .as_ref() - .expect("no known ping pong contract, deploy first") - } -} - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/examples/ping-pong-egld/interactor/tests/ping_pong_interact_cs_test.rs b/contracts/examples/ping-pong-egld/interactor/tests/ping_pong_interact_cs_test.rs index 1ef518c115..fad4d95755 100644 --- a/contracts/examples/ping-pong-egld/interactor/tests/ping_pong_interact_cs_test.rs +++ b/contracts/examples/ping-pong-egld/interactor/tests/ping_pong_interact_cs_test.rs @@ -1,18 +1,37 @@ -use multiversx_sc_snippets::imports::{DurationMillis, RustBigUint, TimestampMillis}; +use multiversx_sc_snippets::imports::*; use ping_pong_egld_interact::{Config, PingPongEgldInteract}; use serial_test::serial; +fn chain_simulator_config() -> Config { + Config { + connection: ConnectionConfig::chain_simulator(), + owner: WalletConfig::from_test_wallet("simon"), + wallet: WalletConfig::from_test_wallet("siobhan"), + } +} + +async fn cs_interactor() -> PingPongEgldInteract { + let config = chain_simulator_config(); + let interactor = Interactor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .use_chain_simulator(true) + .with_config(&config) + .await; + PingPongEgldInteract { + interactor, + config, + state: AutoSave::no_save_default(), + } +} + #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn test_ping_pong_egld() { - let mut interact = PingPongEgldInteract::init(Config::chain_simulator_config()).await; - let wallet_address = interact.wallet_address.clone(); - let ping_pong_owner_address = interact.ping_pong_owner_address.clone(); +async fn test_ping_unmatched_amount() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); let ping_amount = 1u64; - - // test_ping_unmatched_amount let duration = DurationMillis::new(5000u64); let activation_timestamp = TimestampMillis::new(2000u64); let max_funds = 100_000u64; @@ -22,19 +41,22 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, Some(activation_timestamp), - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact - .ping( - 0u64, - Some("the payment must match the fixed sum"), - &wallet_address, - ) - .await; + let err = interact.ping(&wallet_address, 0u64).await.unwrap_err(); + assert_eq!(err.message, "the payment must match the fixed sum"); +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_ping_inactive_contract() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); - // test_ping_inactive_contracts + let ping_amount = 1u64; let duration = DurationMillis::new(5000); let activation_timestamp = TimestampMillis::new(2_000_000_000_000); let max_funds = 100_000u64; @@ -44,15 +66,22 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, Some(activation_timestamp), - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact - .ping(1u64, Some("smart contract not active yet"), &wallet_address) - .await; + let err = interact.ping(&wallet_address, 1u64).await.unwrap_err(); + assert_eq!(err.message, "smart contract not active yet"); +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_ping_passed_deadline() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); - // test_ping_passed_deadline + let ping_amount = 1u64; let duration = DurationMillis::new(5000); let activation_timestamp = TimestampMillis::new(2_000); let max_funds = 100_000u64; @@ -62,15 +91,21 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, Some(activation_timestamp), - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact - .ping(1u64, Some("deadline has passed"), &wallet_address) - .await; + let err = interact.ping(&wallet_address, 1u64).await.unwrap_err(); + assert_eq!(err.message, "deadline has passed"); +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_ping_max_funds() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); - // test_ping_max_funds let ping_amount = 10u64; let duration = DurationMillis::new(30_000_000); let max_funds = 10u64; @@ -80,17 +115,23 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, None, - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact - .ping(10u64, Some("smart contract full"), &wallet_address) - .await; + let err = interact.ping(&wallet_address, 10u64).await.unwrap_err(); + assert_eq!(err.message, "smart contract full"); +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_ping_twice() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); - // test ping let ping_amount = 1u64; - let duration = DurationMillis::new(20_000); + let duration = DurationMillis::new(20_000_000); let max_funds = 100_000u64; interact @@ -98,24 +139,51 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, None, - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact.ping(1u64, None, &wallet_address).await; + interact.ping(&wallet_address, 1u64).await.unwrap(); + let err = interact.ping(&wallet_address, 1u64).await.unwrap_err(); + assert_eq!(err.message, "can only ping once"); +} + +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_pong_before_deadline() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); + + let ping_amount = 1u64; + let duration = DurationMillis::new(20_000_000); + let max_funds = 100_000u64; + interact - .ping(1u64, Some("can only ping once"), &wallet_address) + .deploy( + ping_amount.into(), + duration, + None, + OptionalValue::Some(max_funds.into()), + ) .await; + interact.ping(&wallet_address, 1u64).await.unwrap(); + assert_eq!(interact.get_ping_amount().await, RustBigUint::from(1u64)); - interact - .pong(Some("can't withdraw before deadline"), &wallet_address) - .await; + let err = interact.pong(&wallet_address).await.unwrap_err(); + assert_eq!(err.message, "can't withdraw before deadline"); +} - interact.pong(None, &wallet_address).await; +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_pong_all() { + let mut interact = cs_interactor().await; + let wallet_address = interact.config.wallet.address(); + let ping_pong_owner_address = interact.config.owner.address(); - // test_pong_all let ping_amount = 1u64; let duration = DurationMillis::new(18_000); let max_funds = 100_000u64; @@ -125,16 +193,14 @@ async fn test_ping_pong_egld() { ping_amount.into(), duration, None, - multiversx_sc_snippets::imports::OptionalValue::Some(max_funds.into()), + OptionalValue::Some(max_funds.into()), ) .await; - interact.ping(1u64, None, &ping_pong_owner_address).await; + interact.ping(&ping_pong_owner_address, 1u64).await.unwrap(); + interact.ping(&wallet_address, 1u64).await.unwrap(); - interact.ping(1u64, None, &wallet_address).await; - - interact.pong_all(None, &ping_pong_owner_address).await; - interact - .pong(Some("already withdrawn"), &wallet_address) - .await; + interact.pong_all(&ping_pong_owner_address).await.unwrap(); + let err = interact.pong(&wallet_address).await.unwrap_err(); + assert_eq!(err.message, "already withdrawn"); } diff --git a/contracts/examples/ping-pong-egld/meta/Cargo.toml b/contracts/examples/ping-pong-egld/meta/Cargo.toml index e30dc7b8ee..0c412d4f13 100644 --- a/contracts/examples/ping-pong-egld/meta/Cargo.toml +++ b/contracts/examples/ping-pong-egld/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/proxy-pause/Cargo.toml b/contracts/examples/proxy-pause/Cargo.toml index 33e68c7412..f79e7da241 100644 --- a/contracts/examples/proxy-pause/Cargo.toml +++ b/contracts/examples/proxy-pause/Cargo.toml @@ -9,12 +9,10 @@ publish = false path = "src/proxy_pause.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true [dev-dependencies.check-pause] path = "../check-pause" diff --git a/contracts/examples/proxy-pause/meta/Cargo.toml b/contracts/examples/proxy-pause/meta/Cargo.toml index dbd2b04009..aa44f26b62 100644 --- a/contracts/examples/proxy-pause/meta/Cargo.toml +++ b/contracts/examples/proxy-pause/meta/Cargo.toml @@ -11,6 +11,4 @@ authors = ["you"] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/rewards-distribution/Cargo.toml b/contracts/examples/rewards-distribution/Cargo.toml index 6e270e92ed..f90addf004 100644 --- a/contracts/examples/rewards-distribution/Cargo.toml +++ b/contracts/examples/rewards-distribution/Cargo.toml @@ -9,13 +9,10 @@ publish = false path = "src/rewards_distribution.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/rewards-distribution/meta/Cargo.toml b/contracts/examples/rewards-distribution/meta/Cargo.toml index 37d6200f38..c8947c6511 100644 --- a/contracts/examples/rewards-distribution/meta/Cargo.toml +++ b/contracts/examples/rewards-distribution/meta/Cargo.toml @@ -11,6 +11,4 @@ authors = ["Claudiu-Marcel Bruda "] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/seed-nft-minter/Cargo.toml b/contracts/examples/seed-nft-minter/Cargo.toml index c322bac2f9..49651a36a9 100644 --- a/contracts/examples/seed-nft-minter/Cargo.toml +++ b/contracts/examples/seed-nft-minter/Cargo.toml @@ -9,13 +9,10 @@ publish = false path = "src/seed_nft_minter.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/seed-nft-minter/meta/Cargo.toml b/contracts/examples/seed-nft-minter/meta/Cargo.toml index 155a5d67f1..497ceb5f9d 100644 --- a/contracts/examples/seed-nft-minter/meta/Cargo.toml +++ b/contracts/examples/seed-nft-minter/meta/Cargo.toml @@ -11,6 +11,4 @@ authors = ["Claudiu-Marcel Bruda "] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/examples/storage-examples/.gitignore b/contracts/examples/storage-examples/.gitignore new file mode 100644 index 0000000000..aeba8c9681 --- /dev/null +++ b/contracts/examples/storage-examples/.gitignore @@ -0,0 +1,4 @@ +# Build artifacts — regenerate with `sc-meta all build`. Same convention +# this Cookbook's TypeScript recipes use for node_modules/ and dist/. +target/ +output/ diff --git a/contracts/examples/storage-examples/Cargo.toml b/contracts/examples/storage-examples/Cargo.toml new file mode 100644 index 0000000000..4748f3b7ff --- /dev/null +++ b/contracts/examples/storage-examples/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "storage-examples" +version = "0.0.0" +edition = "2024" +publish = false +authors = ["you"] + +[lib] +path = "src/storage_examples.rs" + +[dependencies.multiversx-sc] +workspace = true + +[dev-dependencies.multiversx-sc-scenario] +workspace = true diff --git a/contracts/examples/storage-examples/README.md b/contracts/examples/storage-examples/README.md new file mode 100644 index 0000000000..10e5b02045 --- /dev/null +++ b/contracts/examples/storage-examples/README.md @@ -0,0 +1,71 @@ +# Storage Examples Contract + +A working MultiversX smart contract demonstrating the six most commonly used storage mapper types side by side. Each mapper gets one endpoint group so the companion blackbox tests can verify the behavioral guarantees in the decision table below. + +It is also available as an `sc-meta` template (see "Using as a template" below). + +## Storage mapper decision table + +| Mapper | Ordering | `contains` | Storage entries (N items) | Iterable | Pick it when | +|---|---|---|---|---|---| +| `SingleValueMapper` | n/a | n/a | 1 | n/a | Exactly one value: a counter, a config flag, a total. | +| `VecMapper` | Insertion order | O(n) | N + 1 | Yes (1-based) | Ordered, indexable, append-friendly; no fast membership check. **Indexes start at 1, not 0.** | +| `SetMapper` | Insertion order | O(1) | ~3N + 1 | Yes, in insertion order | Ordered iteration AND fast membership checks; higher per-element cost than `UnorderedSetMapper`. | +| `UnorderedSetMapper` | None | O(1) | ~2N + 1 | Yes, arbitrary order | Fast membership checks without caring about order — deduplication, processed-ID sets. | +| `WhitelistMapper` | n/a | O(1) | N | **No** | Permission gates where you only ever ask "is X allowed?" and never enumerate members. | +| `MapMapper` | Insertion order of keys | O(1) via `contains_key()` | ~4N + 1 | Yes — `iter()`, `keys()`, `values()` | Key→value store with iteration — balances, per-user settings. Most expensive per element. | + +**Storage entries = unique storage keys, not bytes.** Actual byte cost also depends on the encoded size of `T`. + +Note: `SetMapper` and `UnorderedSetMapper` both provide O(1) `contains()` — the deciding factor between them is ordering and storage cost, not lookup speed. Prefer `UnorderedSetMapper` when insertion order does not matter. + +## Contract endpoints + +| Mapper | Endpoints / Views | +|---|---| +| `SingleValueMapper` | `setCounter`, `getCounter` | +| `VecMapper` | `pushItem`, `getItem`, `itemCount` | +| `SetMapper` | `addToOrderedSet`, `orderedSetContains`, `orderedSetLen` | +| `UnorderedSetMapper` | `addToUnorderedSet`, `unorderedSetContains`, `unorderedSetLen` | +| `WhitelistMapper` | `addToWhitelist`, `isWhitelisted` | +| `MapMapper` | `setBalance`, `getBalance`, `hasBalanceEntry` | + +## Project structure + +``` +storage-examples/ +├── src/ +│ ├── storage_examples.rs # Contract source +│ └── proxy.rs # Auto-generated proxy (sc-meta all proxy) +├── tests/ +│ └── storage_examples_blackbox_test.rs +├── meta/ # Build metadata +├── wasm/ # Wasm build output +├── sc-config.toml # Proxy output path +├── Cargo.toml +└── multiversx.json +``` + +## Building + +```bash +sc-meta all build +``` + +## Regenerating the proxy + +```bash +sc-meta all proxy +``` + +## Testing + +```bash +cargo test +``` + +## Using as a template + +```bash +sc-meta new --template storage-examples --name my-contract +``` diff --git a/contracts/examples/storage-examples/meta/Cargo.toml b/contracts/examples/storage-examples/meta/Cargo.toml new file mode 100644 index 0000000000..5d5dcfa284 --- /dev/null +++ b/contracts/examples/storage-examples/meta/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "storage-examples-meta" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies.storage-examples] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +workspace = true diff --git a/contracts/examples/storage-examples/meta/src/main.rs b/contracts/examples/storage-examples/meta/src/main.rs new file mode 100644 index 0000000000..1fd696d25a --- /dev/null +++ b/contracts/examples/storage-examples/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/contracts/examples/storage-examples/multiversx.json b/contracts/examples/storage-examples/multiversx.json new file mode 100644 index 0000000000..7365539625 --- /dev/null +++ b/contracts/examples/storage-examples/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/examples/storage-examples/mxsc-template.toml b/contracts/examples/storage-examples/mxsc-template.toml new file mode 100644 index 0000000000..2bd9a77bf1 --- /dev/null +++ b/contracts/examples/storage-examples/mxsc-template.toml @@ -0,0 +1,15 @@ +name = "storage-examples" +contract_trait = "StorageExamples" +src_file = "storage_examples.rs" +rename_pairs = [] +files_include = [ + "meta", + "scenarios", + "src", + "tests", + "wasm/Cargo.lock", + "Cargo.toml", + "multiversx.json", + "README.md", + "sc-config.toml", +] diff --git a/contracts/examples/storage-examples/sc-config.toml b/contracts/examples/storage-examples/sc-config.toml new file mode 100644 index 0000000000..7556fbf520 --- /dev/null +++ b/contracts/examples/storage-examples/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "src/proxy.rs" diff --git a/contracts/examples/storage-examples/src/proxy.rs b/contracts/examples/storage-examples/src/proxy.rs new file mode 100644 index 0000000000..e7843840d0 --- /dev/null +++ b/contracts/examples/storage-examples/src/proxy.rs @@ -0,0 +1,278 @@ +// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +#![allow(dead_code)] +#![allow(clippy::all)] + +use multiversx_sc::proxy_imports::*; + +pub struct StorageExamplesProxy; + +impl TxProxyTrait for StorageExamplesProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = StorageExamplesProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + StorageExamplesProxyMethods { wrapped_tx: tx } + } +} + +pub struct StorageExamplesProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl StorageExamplesProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init( + self, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .original_result() + } +} + +#[rustfmt::skip] +impl StorageExamplesProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn upgrade( + self, + ) -> TxTypedUpgrade { + self.wrapped_tx + .payment(NotPayable) + .raw_upgrade() + .original_result() + } +} + +#[rustfmt::skip] +impl StorageExamplesProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn set_counter< + Arg0: ProxyArg>, + >( + self, + value: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setCounter") + .argument(&value) + .original_result() + } + + pub fn counter( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getCounter") + .original_result() + } + + pub fn push_item< + Arg0: ProxyArg>, + >( + self, + item: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("pushItem") + .argument(&item) + .original_result() + } + + pub fn get_item< + Arg0: ProxyArg, + >( + self, + index: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getItem") + .argument(&index) + .original_result() + } + + pub fn item_count( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("itemCount") + .original_result() + } + + pub fn add_to_ordered_set< + Arg0: ProxyArg, + >( + self, + value: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addToOrderedSet") + .argument(&value) + .original_result() + } + + pub fn ordered_set_contains< + Arg0: ProxyArg, + >( + self, + value: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("orderedSetContains") + .argument(&value) + .original_result() + } + + pub fn ordered_set_len( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("orderedSetLen") + .original_result() + } + + pub fn add_to_unordered_set< + Arg0: ProxyArg, + >( + self, + value: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addToUnorderedSet") + .argument(&value) + .original_result() + } + + pub fn unordered_set_contains< + Arg0: ProxyArg, + >( + self, + value: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("unorderedSetContains") + .argument(&value) + .original_result() + } + + pub fn unordered_set_len( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("unorderedSetLen") + .original_result() + } + + pub fn add_to_whitelist< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addToWhitelist") + .argument(&address) + .original_result() + } + + pub fn is_whitelisted< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("isWhitelisted") + .argument(&address) + .original_result() + } + + pub fn set_balance< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + address: Arg0, + amount: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setBalance") + .argument(&address) + .argument(&amount) + .original_result() + } + + pub fn get_balance< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getBalance") + .argument(&address) + .original_result() + } + + pub fn has_balance_entry< + Arg0: ProxyArg>, + >( + self, + address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("hasBalanceEntry") + .argument(&address) + .original_result() + } +} diff --git a/contracts/examples/storage-examples/src/storage_examples.rs b/contracts/examples/storage-examples/src/storage_examples.rs new file mode 100644 index 0000000000..8a255bd6c0 --- /dev/null +++ b/contracts/examples/storage-examples/src/storage_examples.rs @@ -0,0 +1,154 @@ +#![no_std] + +use multiversx_sc::imports::*; + +pub mod proxy; + +/// Demonstrates the six most commonly used storage mapper types in +/// `multiversx-sc`, one endpoint group per mapper. Each group exposes a +/// minimal but realistic API so the companion blackbox tests can verify the +/// behavioural guarantees documented in the mapper source. +/// +/// | Mapper | Iteration | Duplicates | `contains` | Storage entries | +/// |----------------------|------------|------------|------------|-------------------| +/// | `SingleValueMapper` | — | one slot | — | 1 | +/// | `VecMapper` | sequential | allowed | O(n) | N + 1 | +/// | `SetMapper` | ordered | rejected | O(1) | ~3N + 1 | +/// | `UnorderedSetMapper` | unordered | rejected | O(1) | 2N + 1 | +/// | `WhitelistMapper` | — | rejected | O(1) | N | +/// | `MapMapper` | keys only | — | O(1) | 4N + 1 | +#[multiversx_sc::contract] +pub trait StorageExamples { + #[init] + fn init(&self) {} + + #[upgrade] + fn upgrade(&self) {} + + // ---- SingleValueMapper ----------------------------------------------- + // Stores exactly one value under a fixed key. The simplest mapper: + // one storage entry, no iteration, O(1) read/write. + // `set` overwrites any previous value; `get` returns the default if + // the slot has never been written. + #[endpoint(setCounter)] + fn set_counter(&self, value: BigUint) { + self.counter().set(value); + } + + #[view(getCounter)] + #[storage_mapper("counter")] + fn counter(&self) -> SingleValueMapper; + + // ---- VecMapper --------------------------------------------------------- + // An ordered, 1-indexed sequence that allows duplicate values. + // `push` appends to the end and returns the new item's 1-based index. + // Random access by index is O(1); `contains` is O(n) (linear scan). + #[endpoint(pushItem)] + fn push_item(&self, item: ManagedBuffer) -> usize { + self.items().push(&item) + } + + #[view(getItem)] + fn get_item(&self, index: usize) -> ManagedBuffer { + self.items().get(index) + } + + #[view(itemCount)] + fn item_count(&self) -> usize { + self.items().len() + } + + #[storage_mapper("items")] + fn items(&self) -> VecMapper; + + // ---- SetMapper -------------------------------------------------------- + // An ordered set (insertion order) that rejects duplicates. + // Internally maintains a doubly-linked list of nodes plus a + // value→node-id index, giving O(1) `contains`, `insert`, and `remove`. + // `insert` returns `true` when the value was newly added, `false` if + // it was already present. Uses ~3N + 1 storage entries for N elements. + #[endpoint(addToOrderedSet)] + fn add_to_ordered_set(&self, value: u64) -> bool { + self.ordered_set().insert(value) + } + + #[view(orderedSetContains)] + fn ordered_set_contains(&self, value: u64) -> bool { + self.ordered_set().contains(&value) + } + + #[view(orderedSetLen)] + fn ordered_set_len(&self) -> usize { + self.ordered_set().len() + } + + #[storage_mapper("ordered_set")] + fn ordered_set(&self) -> SetMapper; + + // ---- UnorderedSetMapper ----------------------------------------------- + // A set with no ordering guarantee. Backed by a `VecMapper` plus a + // reverse value→index map, giving O(1) `contains`, `insert`, and + // `remove` (swap-remove strategy) at the cost of ~2N + 1 storage + // entries. Cheaper than `SetMapper` when iteration order does not + // matter. `insert` returns `true` for a new value, `false` for a + // duplicate. + #[endpoint(addToUnorderedSet)] + fn add_to_unordered_set(&self, value: u64) -> bool { + self.unordered_set().insert(value) + } + + #[view(unorderedSetContains)] + fn unordered_set_contains(&self, value: u64) -> bool { + self.unordered_set().contains(&value) + } + + #[view(unorderedSetLen)] + fn unordered_set_len(&self) -> usize { + self.unordered_set().len() + } + + #[storage_mapper("unordered_set")] + fn unordered_set(&self) -> UnorderedSetMapper; + + // ---- WhitelistMapper -------------------------------------------------- + // Membership-only set: supports `add`, `remove`, and `contains` but + // provides no iteration. Each member costs exactly one storage entry + // (a boolean flag), making it the most space-efficient option when + // you only need to answer "is this address a member?". + #[endpoint(addToWhitelist)] + fn add_to_whitelist(&self, address: ManagedAddress) { + self.whitelist().add(&address); + } + + #[view(isWhitelisted)] + fn is_whitelisted(&self, address: ManagedAddress) -> bool { + self.whitelist().contains(&address) + } + + #[storage_mapper("whitelist")] + fn whitelist(&self) -> WhitelistMapper; + + // ---- MapMapper -------------------------------------------------------- + // Key-value store with iterable keys. Uses a `SetMapper` internally + // to track the key set plus one extra slot per value, totalling ~4N + 1 + // storage entries for N pairs. `insert` and `get` are O(1); iterating + // all keys is O(N). `get` returns `Option` — use `unwrap_or_default` + // when a missing key should be treated as a zero/empty value. + #[endpoint(setBalance)] + fn set_balance(&self, address: ManagedAddress, amount: BigUint) { + self.balances().insert(address, amount); + } + + #[view(getBalance)] + fn get_balance(&self, address: ManagedAddress) -> BigUint { + self.balances().get(&address).unwrap_or_default() + } + + #[view(hasBalanceEntry)] + fn has_balance_entry(&self, address: ManagedAddress) -> bool { + self.balances().contains_key(&address) + } + + #[storage_mapper("balances")] + fn balances(&self) -> MapMapper; +} diff --git a/contracts/examples/storage-examples/tests/storage_examples_blackbox_test.rs b/contracts/examples/storage-examples/tests/storage_examples_blackbox_test.rs new file mode 100644 index 0000000000..3fecb7410d --- /dev/null +++ b/contracts/examples/storage-examples/tests/storage_examples_blackbox_test.rs @@ -0,0 +1,337 @@ +// tests/storage_mappers_blackbox_test.rs — one test per mapper this +// recipe covers, each proving the specific behavioral claim its README +// entry makes (not just "it compiles"). + +use multiversx_sc_scenario::imports::*; + +use storage_examples::proxy; + +const OWNER: TestAddress = TestAddress::new("owner"); +const CONTRACT: TestSCAddress = TestSCAddress::new("storage-examples-contract"); +const CODE_PATH: MxscPath = MxscPath::new("output/storage-examples.mxsc.json"); + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.register_contract(CODE_PATH, storage_examples::ContractBuilder); + blockchain +} + +fn deploy(world: &mut ScenarioWorld) { + world.account(OWNER).nonce(1); + world + .tx() + .from(OWNER) + .typed(proxy::StorageExamplesProxy) + .init() + .code(CODE_PATH) + .new_address(CONTRACT) + .run(); +} + +#[test] +fn single_value_mapper_set_and_get() { + let mut world = world(); + deploy(&mut world); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .set_counter(BigUint::::from(42u64)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .counter() + .returns(ExpectValue(BigUint::::from(42u64))) + .run(); +} + +#[test] +fn single_value_mapper_overwrite() { + let mut world = world(); + deploy(&mut world); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .set_counter(BigUint::::from(10u64)) + .run(); + + // A second set must overwrite the first — not accumulate. + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .set_counter(BigUint::::from(99u64)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .counter() + .returns(ExpectValue(BigUint::::from(99u64))) + .run(); +} + +#[test] +fn vec_mapper_is_one_indexed() { + let mut world = world(); + deploy(&mut world); + + // Push returns the 1-based index of the newly appended item. + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .push_item(ManagedBuffer::::from(b"first")) + .returns(ExpectValue(1usize)) + .run(); + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .push_item(ManagedBuffer::::from(b"second")) + .returns(ExpectValue(2usize)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .item_count() + .returns(ExpectValue(2usize)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .get_item(1usize) + .returns(ExpectValue(ManagedBuffer::::from(b"first"))) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .get_item(2usize) + .returns(ExpectValue(ManagedBuffer::::from(b"second"))) + .run(); +} + +#[test] +fn set_mapper_contains_and_ordering() { + let mut world = world(); + deploy(&mut world); + + for value in [30u64, 10u64, 20u64] { + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .add_to_ordered_set(value) + // A fresh insert must return true. + .returns(ExpectValue(true)) + .run(); + } + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .ordered_set_contains(10u64) + .returns(ExpectValue(true)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .ordered_set_contains(99u64) + .returns(ExpectValue(false)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .ordered_set_len() + .returns(ExpectValue(3usize)) + .run(); + + // Inserting a duplicate must return false and leave the length unchanged. + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .add_to_ordered_set(10u64) + .returns(ExpectValue(false)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .ordered_set_len() + .returns(ExpectValue(3usize)) + .run(); +} + +#[test] +fn unordered_set_mapper_contains_after_insert_and_absent_value() { + let mut world = world(); + deploy(&mut world); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .add_to_unordered_set(7u64) + // A fresh insert must return true. + .returns(ExpectValue(true)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .unordered_set_contains(7u64) + .returns(ExpectValue(true)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .unordered_set_contains(8u64) + .returns(ExpectValue(false)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .unordered_set_len() + .returns(ExpectValue(1usize)) + .run(); + + // Inserting a duplicate must return false and leave the length unchanged. + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .add_to_unordered_set(7u64) + .returns(ExpectValue(false)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .unordered_set_len() + .returns(ExpectValue(1usize)) + .run(); +} + +#[test] +fn whitelist_mapper_membership_only() { + let mut world = world(); + deploy(&mut world); + let allowed: TestAddress = TestAddress::new("allowed-user"); + let stranger: TestAddress = TestAddress::new("stranger"); + world.account(allowed).nonce(1); + world.account(stranger).nonce(1); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .add_to_whitelist(allowed.to_address()) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .is_whitelisted(allowed.to_address()) + .returns(ExpectValue(true)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .is_whitelisted(stranger.to_address()) + .returns(ExpectValue(false)) + .run(); +} + +#[test] +fn map_mapper_insert_get_and_contains_key() { + let mut world = world(); + deploy(&mut world); + let holder: TestAddress = TestAddress::new("balance-holder"); + let nobody: TestAddress = TestAddress::new("no-balance"); + world.account(holder).nonce(1); + world.account(nobody).nonce(1); + + world + .tx() + .from(OWNER) + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .set_balance(holder.to_address(), BigUint::::from(500u64)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .get_balance(holder.to_address()) + .returns(ExpectValue(BigUint::::from(500u64))) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .has_balance_entry(holder.to_address()) + .returns(ExpectValue(true)) + .run(); + + // A key that was never inserted: contains_key is false, and the + // convenience getter's unwrap_or_default() reads as zero rather than + // erroring — a real design choice worth testing explicitly, since it + // means "balance of zero" and "never had an entry" are + // indistinguishable through get_balance alone. + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .has_balance_entry(nobody.to_address()) + .returns(ExpectValue(false)) + .run(); + + world + .query() + .to(CONTRACT) + .typed(proxy::StorageExamplesProxy) + .get_balance(nobody.to_address()) + .returns(ExpectValue(BigUint::::from(0u64))) + .run(); +} diff --git a/contracts/examples/storage-examples/wasm/Cargo.toml b/contracts/examples/storage-examples/wasm/Cargo.toml new file mode 100644 index 0000000000..abe9683ed1 --- /dev/null +++ b/contracts/examples/storage-examples/wasm/Cargo.toml @@ -0,0 +1,35 @@ +# Code generated by the multiversx-sc build system. DO NOT EDIT. + +# ########################################## +# ############## AUTO-GENERATED ############# +# ########################################## + +[package] +name = "storage-examples-wasm" +version = "0.0.0" +edition = "2024" +publish = false + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" +overflow-checks = false + +[profile.dev] +panic = "abort" + +[dependencies.storage-examples] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.66.2" +path = "../../../../framework/wasm-adapter" + +[workspace] +members = ["."] diff --git a/contracts/examples/storage-examples/wasm/src/lib.rs b/contracts/examples/storage-examples/wasm/src/lib.rs new file mode 100644 index 0000000000..05982514c2 --- /dev/null +++ b/contracts/examples/storage-examples/wasm/src/lib.rs @@ -0,0 +1,42 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 16 +// Async Callback (empty): 1 +// Total number of exported functions: 19 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + storage_examples + ( + init => init + upgrade => upgrade + setCounter => set_counter + getCounter => counter + pushItem => push_item + getItem => get_item + itemCount => item_count + addToOrderedSet => add_to_ordered_set + orderedSetContains => ordered_set_contains + orderedSetLen => ordered_set_len + addToUnorderedSet => add_to_unordered_set + unorderedSetContains => unordered_set_contains + unorderedSetLen => unordered_set_len + addToWhitelist => add_to_whitelist + isWhitelisted => is_whitelisted + setBalance => set_balance + getBalance => get_balance + hasBalanceEntry => has_balance_entry + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/contracts/examples/token-release/Cargo.toml b/contracts/examples/token-release/Cargo.toml index af5d693e25..c62f4c8f08 100644 --- a/contracts/examples/token-release/Cargo.toml +++ b/contracts/examples/token-release/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/token_release.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/examples/token-release/meta/Cargo.toml b/contracts/examples/token-release/meta/Cargo.toml index 8ec07047b5..c00b671af4 100644 --- a/contracts/examples/token-release/meta/Cargo.toml +++ b/contracts/examples/token-release/meta/Cargo.toml @@ -10,6 +10,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/abi-tester/Cargo.toml b/contracts/feature-tests/abi-tester/Cargo.toml index 26d5cbe237..3f1632fab8 100644 --- a/contracts/feature-tests/abi-tester/Cargo.toml +++ b/contracts/feature-tests/abi-tester/Cargo.toml @@ -9,17 +9,14 @@ publish = false path = "src/abi_tester.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true [dev-dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../framework/meta-lib" +workspace = true [dependencies] -bitflags = "2.9" +bitflags = "2.13" diff --git a/contracts/feature-tests/abi-tester/meta/Cargo.toml b/contracts/feature-tests/abi-tester/meta/Cargo.toml index d79ef1e8c8..b04b386843 100644 --- a/contracts/feature-tests/abi-tester/meta/Cargo.toml +++ b/contracts/feature-tests/abi-tester/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/alloc-features/Cargo.toml b/contracts/feature-tests/alloc-features/Cargo.toml index 62664d39e5..a568a620a3 100644 --- a/contracts/feature-tests/alloc-features/Cargo.toml +++ b/contracts/feature-tests/alloc-features/Cargo.toml @@ -9,11 +9,9 @@ publish = false path = "src/alloc_features_main.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/alloc-features/meta/Cargo.toml b/contracts/feature-tests/alloc-features/meta/Cargo.toml index d5ea96c4e1..5f88766d2b 100644 --- a/contracts/feature-tests/alloc-features/meta/Cargo.toml +++ b/contracts/feature-tests/alloc-features/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/basic-features/Cargo.toml b/contracts/feature-tests/basic-features/Cargo.toml index 8aab7488fd..67f45bc12e 100644 --- a/contracts/feature-tests/basic-features/Cargo.toml +++ b/contracts/feature-tests/basic-features/Cargo.toml @@ -12,23 +12,19 @@ path = "src/basic_features_main.rs" small-int-bug = ["multiversx-sc/small-int-bug"] [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true # BLS doesn't currently work on Windows [target.'cfg(not(target_os = "windows"))'.dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental", "bls"] -path = "../../../framework/scenario" [target.'cfg(target_os = "windows")'.dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dev-dependencies.esdt-system-sc-mock] path = "../esdt-system-sc-mock" diff --git a/contracts/feature-tests/basic-features/interact/Cargo.toml b/contracts/feature-tests/basic-features/interact/Cargo.toml index ed3dfea7ff..639500c3f9 100644 --- a/contracts/feature-tests/basic-features/interact/Cargo.toml +++ b/contracts/feature-tests/basic-features/interact/Cargo.toml @@ -26,8 +26,7 @@ serial_test = "3.2" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [features] chain-simulator-tests = [] diff --git a/contracts/feature-tests/basic-features/interact/config.toml b/contracts/feature-tests/basic-features/interact/config.toml index 3a71d98ce1..9e6b79b4f3 100644 --- a/contracts/feature-tests/basic-features/interact/config.toml +++ b/contracts/feature-tests/basic-features/interact/config.toml @@ -1,5 +1,10 @@ -chain_type = 'real' -gateway_uri = 'https://testnet-gateway.multiversx.com' +[connection] +# chain_type = 'real' +# gateway_uri = 'https://devnet-gateway.multiversx.com' + +chain_type = 'simulator' +gateway_uri = 'http://localhost:8085' + +[wallet] +test_wallet = 'mike' -# chain_type = 'simulator' -# gateway_uri = 'http://localhost:8085' diff --git a/contracts/feature-tests/basic-features/interact/src/bf_interact.rs b/contracts/feature-tests/basic-features/interact/src/bf_interact.rs index 9bc513a895..1a31092019 100644 --- a/contracts/feature-tests/basic-features/interact/src/bf_interact.rs +++ b/contracts/feature-tests/basic-features/interact/src/bf_interact.rs @@ -10,7 +10,7 @@ use clap::Parser; use multiversx_sc_snippets::imports::*; -const INTERACTOR_SCENARIO_TRACE_PATH: &str = "interactor_trace.scen.json"; +pub const INTERACTOR_SCENARIO_TRACE_PATH: &str = "interactor_trace.scen.json"; const CODE_EXPR_STORAGE_BYTES: MxscPath = MxscPath::new("../output/basic-features-storage-bytes.mxsc.json"); @@ -20,9 +20,7 @@ const CODE_EXPR: MxscPath = MxscPath::new("../output/basic-features.mxsc.json"); pub async fn basic_features_cli() { env_logger::init(); - let config = Config::load_config(); - - let mut bf_interact = BasicFeaturesInteract::init(config).await; + let mut bf_interact = BasicFeaturesInteract::init().await; let cli = bf_interact_cli::InteractCli::parse(); match cli.command { @@ -72,27 +70,20 @@ pub async fn basic_features_cli() { pub struct BasicFeaturesInteract { pub interactor: Interactor, pub wallet_address: Bech32Address, - pub state: State, + pub state: AutoSave, pub large_storage_payload: Vec, } impl BasicFeaturesInteract { - pub async fn init(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()) - .with_tracer(INTERACTOR_SCENARIO_TRACE_PATH) - .await; - interactor - .set_current_dir_from_workspace("contracts/feature-tests/basic-features/interact"); - let wallet_address = interactor.register_wallet(test_wallets::mike()).await; - - interactor.generate_blocks_until_all_activations().await; - + pub async fn init() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + let wallet_address = config.wallet.address(); Self { interactor, - wallet_address: wallet_address.into(), - state: State::load_state(), + wallet_address, + state, large_storage_payload: Vec::new(), } } diff --git a/contracts/feature-tests/basic-features/interact/src/bf_interact_config.rs b/contracts/feature-tests/basic-features/interact/src/bf_interact_config.rs index bd19da629d..98faa57cb1 100644 --- a/contracts/feature-tests/basic-features/interact/src/bf_interact_config.rs +++ b/contracts/feature-tests/basic-features/interact/src/bf_interact_config.rs @@ -1,49 +1,19 @@ +use multiversx_sc_snippets::imports::*; use serde::Deserialize; -use std::io::Read; -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Adder Interact configuration +/// Basic Features Interact configuration #[derive(Debug, Deserialize)] pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, + pub connection: ConnectionConfig, + pub wallet: WalletConfig, } -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection } - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } + fn register_wallets(&self) -> Vec { + vec![self.wallet.wallet().clone()] } } diff --git a/contracts/feature-tests/basic-features/interact/src/bf_interact_state.rs b/contracts/feature-tests/basic-features/interact/src/bf_interact_state.rs index 68ac67a960..517b94ce32 100644 --- a/contracts/feature-tests/basic-features/interact/src/bf_interact_state.rs +++ b/contracts/feature-tests/basic-features/interact/src/bf_interact_state.rs @@ -1,12 +1,5 @@ use multiversx_sc_snippets::imports::Bech32Address; use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; /// Basic Features Interact state #[derive(Debug, Default, Serialize, Deserialize)] @@ -17,18 +10,6 @@ pub struct State { } impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - /// Sets the contract address for basic-features-storage-bytes pub fn set_bf_address_storage_bytes(&mut self, address: Bech32Address) { self.bf_address_storage_bytes = Some(address); @@ -53,12 +34,3 @@ impl State { .expect("basic-features contract not yet deployed") } } - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/feature-tests/basic-features/interact/tests/bf_interact_cs_test.rs b/contracts/feature-tests/basic-features/interact/tests/bf_interact_cs_test.rs index 2b76ebc214..5690fc79fe 100644 --- a/contracts/feature-tests/basic-features/interact/tests/bf_interact_cs_test.rs +++ b/contracts/feature-tests/basic-features/interact/tests/bf_interact_cs_test.rs @@ -7,11 +7,33 @@ use system_sc_interact::SysFuncCallsInteract; const ISSUE_COST: u64 = 50000000000000000u64; +fn chain_simulator_config() -> Config { + Config { + connection: ConnectionConfig::chain_simulator(), + wallet: WalletConfig::from_test_wallet("mike"), + } +} + +async fn cs_interactor() -> BasicFeaturesInteract { + let config = chain_simulator_config(); + let interactor = HttpInteractor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&config) + .await; + let wallet_address = config.wallet.address(); + BasicFeaturesInteract { + interactor, + wallet_address, + state: AutoSave::no_save_default(), + large_storage_payload: Vec::new(), + } +} + #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn simulator_basic_features_test() { - let mut bf_interact = BasicFeaturesInteract::init(Config::chain_simulator_config()).await; + let mut bf_interact = cs_interactor().await; bf_interact.add_validator_key().await; bf_interact.deploy_storage_bytes(false).await; @@ -50,8 +72,7 @@ async fn send_esdt_to_non_existent_address_test() { let registered_wallet_address = test_wallets::mike().to_address(); let not_registered_wallet_address = test_wallets::alice().to_address(); - let mut chain_simulator_interact = - BasicFeaturesInteract::init(Config::chain_simulator_config()).await; + let mut chain_simulator_interact = cs_interactor().await; let token = chain_simulator_interact .interactor @@ -96,7 +117,7 @@ async fn send_esdt_to_non_existent_address_test() { #[serial] #[ignore = "signature verification is currently unavailable"] async fn simulator_crypto_test() { - let mut bf_interact = BasicFeaturesInteract::init(Config::chain_simulator_config()).await; + let mut bf_interact = cs_interactor().await; bf_interact.deploy().await; @@ -258,7 +279,7 @@ async fn verify_bls_aggregated_signature(interact: &mut BasicFeaturesInteract) { #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn chain_simulator_bf_get_special_roles_test() { - let mut bf_interact = BasicFeaturesInteract::init(Config::chain_simulator_config()).await; + let mut bf_interact = cs_interactor().await; let mut system_interact = SysFuncCallsInteract::init(system_sc_interact::Config::chain_simulator_config()).await; diff --git a/contracts/feature-tests/basic-features/meta/Cargo.toml b/contracts/feature-tests/basic-features/meta/Cargo.toml index aef691503e..e54614f574 100644 --- a/contracts/feature-tests/basic-features/meta/Cargo.toml +++ b/contracts/feature-tests/basic-features/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/big-float-features/Cargo.toml b/contracts/feature-tests/big-float-features/Cargo.toml index 269cb2abab..15bcdc6cd6 100644 --- a/contracts/feature-tests/big-float-features/Cargo.toml +++ b/contracts/feature-tests/big-float-features/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/big_float_main.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/big-float-features/meta/Cargo.toml b/contracts/feature-tests/big-float-features/meta/Cargo.toml index 2ddccdc323..cec51db327 100644 --- a/contracts/feature-tests/big-float-features/meta/Cargo.toml +++ b/contracts/feature-tests/big-float-features/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/Cargo.toml b/contracts/feature-tests/composability/Cargo.toml index 2cc3620fe7..2b879d8bc7 100644 --- a/contracts/feature-tests/composability/Cargo.toml +++ b/contracts/feature-tests/composability/Cargo.toml @@ -36,10 +36,8 @@ path = "recursive-caller" path = "vault" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true features = ["wasmer-experimental"] diff --git a/contracts/feature-tests/composability/builtin-func-features/Cargo.toml b/contracts/feature-tests/composability/builtin-func-features/Cargo.toml index 782f86af78..4e860c3a5b 100644 --- a/contracts/feature-tests/composability/builtin-func-features/Cargo.toml +++ b/contracts/feature-tests/composability/builtin-func-features/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/builtin_func_features.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/builtin-func-features/meta/Cargo.toml b/contracts/feature-tests/composability/builtin-func-features/meta/Cargo.toml index 1ff5ef0a01..78ad027e49 100644 --- a/contracts/feature-tests/composability/builtin-func-features/meta/Cargo.toml +++ b/contracts/feature-tests/composability/builtin-func-features/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/esdt-contract-pair/Cargo.toml b/contracts/feature-tests/composability/esdt-contract-pair/Cargo.toml index ad2c2d46a4..9762b04906 100644 --- a/contracts/feature-tests/composability/esdt-contract-pair/Cargo.toml +++ b/contracts/feature-tests/composability/esdt-contract-pair/Cargo.toml @@ -12,9 +12,7 @@ path = "first-contract" path = "second-contract" [dev-dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/esdt-contract-pair/first-contract/Cargo.toml b/contracts/feature-tests/composability/esdt-contract-pair/first-contract/Cargo.toml index 447f0fe8d0..72278618e5 100644 --- a/contracts/feature-tests/composability/esdt-contract-pair/first-contract/Cargo.toml +++ b/contracts/feature-tests/composability/esdt-contract-pair/first-contract/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/esdt-contract-pair/first-contract/meta/Cargo.toml b/contracts/feature-tests/composability/esdt-contract-pair/first-contract/meta/Cargo.toml index d20082620f..40b9c7e7a8 100644 --- a/contracts/feature-tests/composability/esdt-contract-pair/first-contract/meta/Cargo.toml +++ b/contracts/feature-tests/composability/esdt-contract-pair/first-contract/meta/Cargo.toml @@ -8,10 +8,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/esdt-contract-pair/second-contract/Cargo.toml b/contracts/feature-tests/composability/esdt-contract-pair/second-contract/Cargo.toml index 79c2437700..ae1c708b6e 100644 --- a/contracts/feature-tests/composability/esdt-contract-pair/second-contract/Cargo.toml +++ b/contracts/feature-tests/composability/esdt-contract-pair/second-contract/Cargo.toml @@ -10,10 +10,8 @@ path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/esdt-contract-pair/second-contract/meta/Cargo.toml b/contracts/feature-tests/composability/esdt-contract-pair/second-contract/meta/Cargo.toml index ddba693202..d6c72fee56 100644 --- a/contracts/feature-tests/composability/esdt-contract-pair/second-contract/meta/Cargo.toml +++ b/contracts/feature-tests/composability/esdt-contract-pair/second-contract/meta/Cargo.toml @@ -8,10 +8,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/Cargo.toml b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/Cargo.toml index 0a24a4fbcc..299eb8445f 100644 --- a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/Cargo.toml +++ b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/Cargo.toml @@ -16,9 +16,7 @@ path = "parent" path = "child" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/Cargo.toml b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/Cargo.toml index 6c29d6e973..674492bd6a 100644 --- a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/Cargo.toml +++ b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/Cargo.toml @@ -10,9 +10,7 @@ path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/meta/Cargo.toml b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/meta/Cargo.toml index 4d8734d2b4..af95cd17c2 100644 --- a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/meta/Cargo.toml +++ b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/child/meta/Cargo.toml @@ -8,10 +8,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/Cargo.toml b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/Cargo.toml index a395f696ad..b7c765e270 100644 --- a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/Cargo.toml +++ b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/meta/Cargo.toml b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/meta/Cargo.toml index 4066682ce8..30aec26048 100644 --- a/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/meta/Cargo.toml +++ b/contracts/feature-tests/composability/execute-on-dest-esdt-issue-callback/parent/meta/Cargo.toml @@ -8,10 +8,7 @@ publish = false path = ".." [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../../framework/base" +workspace = true [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-blind/Cargo.toml b/contracts/feature-tests/composability/forwarder-blind/Cargo.toml index 773a5aaae2..eef005119d 100644 --- a/contracts/feature-tests/composability/forwarder-blind/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-blind/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/forwarder_blind.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/Cargo.toml b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/Cargo.toml index e05a59f9ab..a6799fc7bd 100644 --- a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/Cargo.toml @@ -16,12 +16,10 @@ path = "src/dex_interactor.rs" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../../framework/snippets" +workspace = true [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../../framework/base" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/config.toml b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/config.toml index 6d2096c439..1da474e939 100644 --- a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/config.toml +++ b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/config.toml @@ -1,18 +1,26 @@ +[connection] chain_type = 'real' gateway_uri = 'https://gateway.battleofnodes.com' + +[general] +contract_path = 'forwarder-blind-bon.wasm' wegld_address = 'erd1qqqqqqqqqqqqqpgqmuk0q2saj0mgutxm4teywre6dl8wqf58xamqdrukln' pair_address = 'erd1qqqqqqqqqqqqqpgqeel2kumf0r8ffyhth7pqdujjat9nx0862jpsg2pqaq' wegld_token_id = 'WEGLD-bd4d79' usdc_token_id = 'USDC-c76f1f' -# Optional: list of PEM file paths, one per wallet to use. -# Relative paths are resolved from the workspace root. +# Optional: list of wallets, one per entry. # If absent or empty, all operations are skipped with a warning. -wallet_pem_paths = [ - '../../../../../sdk/core/src/test_wallets/s0phie.pem', - '../../../../../sdk/core/src/test_wallets/s1mon.pem', - '../../../../../sdk/core/src/test_wallets/s2onja.pem', -] +# Provide exactly one of: test_wallet, pem, or keyfile per entry. + +[[wallets]] +test_wallet = 's0phie' + +[[wallets]] +test_wallet = 's1mon' + +[[wallets]] +test_wallet = 's2onja' contract_addresses = [ "erd1qqqqqqqqqqqqqpgqwlqhw27xz6j2cqh8w8v53l5fdulfu4rqqqqqdt5qat", diff --git a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/config.rs b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/config.rs index 3d35813207..2eaca723db 100644 --- a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/config.rs +++ b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/config.rs @@ -1,54 +1,33 @@ -use multiversx_sc_snippets::imports::Bech32Address; +use multiversx_sc_snippets::imports::*; use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; +/// General settings for the forwarder-blind dex interact. #[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, +pub struct GeneralConfig { + pub contract_path: ConfigPath, + pub wegld_address: Bech32Address, + pub pair_address: Bech32Address, + pub wegld_token_id: String, + pub usdc_token_id: String, } /// Contract Interact configuration #[derive(Debug, Deserialize)] pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, - pub wegld_address: Bech32Address, - pub pair_address: Bech32Address, - pub wegld_token_id: String, - pub usdc_token_id: String, - /// Optional list of PEM file paths, one per wallet. - /// If absent or empty, all operations are skipped with a warning. - #[serde(default)] - pub wallet_pem_paths: Vec, + pub connection: ConnectionConfig, + pub general: GeneralConfig, + pub wallets: Vec, /// Forwarder contract addresses to target for all swap transactions. #[serde(default)] pub contract_addresses: Vec, } -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection } - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } + fn register_wallets(&self) -> Vec { + self.wallets.iter().map(|w| w.wallet().clone()).collect() } } diff --git a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/dex_interactor.rs b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/dex_interactor.rs index ea95c31891..ee7275bfd3 100644 --- a/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/dex_interactor.rs +++ b/contracts/feature-tests/composability/forwarder-blind/dex-interactor/src/dex_interactor.rs @@ -10,13 +10,10 @@ use multiversx_sc_snippets::imports::*; use proxies::*; use state::State; -const FORWARDER_BLIND_CODE_PATH: FilePath = FilePath("forwarder-blind-bon.wasm"); - pub async fn forwarder_blind_cli() { env_logger::init(); - let config = Config::load_config(); - let mut interact = ContractInteract::new(config).await; + let mut interact = ContractInteract::new().await; let cli = cli::InteractCli::parse(); match &cli.command { @@ -112,28 +109,22 @@ pub struct ContractInteract { } impl ContractInteract { - pub async fn new(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()).await; + pub async fn new() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; interactor.gas_price *= 25; - interactor.set_current_dir_from_workspace( - "contracts/feature-tests/composability/forwarder-blind/dex-interactor", - ); - let wallet_addresses: Vec = if config.wallet_pem_paths.is_empty() { - println!("WARNING: no wallet_pem_paths configured — all operations will be skipped."); + let wallet_addresses: Vec = if config.wallets.is_empty() { + println!("WARNING: no wallets configured — all operations will be skipped."); Vec::new() } else { - let mut addrs = Vec::new(); - for pem_path in &config.wallet_pem_paths { - let wallet = Wallet::from_pem_file(pem_path) - .unwrap_or_else(|e| panic!("failed to load wallet from {pem_path}: {e}")); - addrs.push(interactor.register_wallet(wallet).await.into()); - } - addrs + config + .wallets + .iter() + .map(|w| w.wallet().to_address().to_bech32(interactor.get_hrp())) + .collect() }; - interactor.generate_blocks_until_all_activations().await; - ContractInteract { interactor, wallet_addresses, @@ -151,7 +142,7 @@ impl ContractInteract { .gas(80_000_000u64) .typed(forwarder_blind_proxy::ForwarderBlindProxy) .init() - .code(FORWARDER_BLIND_CODE_PATH) + .code(&self.config.general.contract_path) .code_metadata(CodeMetadata::PAYABLE) .returns(ReturnsNewBech32Address) }); @@ -165,7 +156,7 @@ impl ContractInteract { pub async fn wrap_egld(&mut self, amount: u64) { let wallet_addresses = self.wallet_addresses.clone(); - let wegld_address = self.config.wegld_address.clone(); + let wegld_address = self.config.general.wegld_address.clone(); let mut buffer = self.interactor.homogenous_call_buffer(); for wallet in &wallet_addresses { buffer.push_tx(|tx| { @@ -212,7 +203,7 @@ impl ContractInteract { .interactor .tx() .from(&wallet) - .to(&self.config.pair_address) + .to(&self.config.general.pair_address) .gas(50_000_000u64) .raw_data(fc) .payment( @@ -237,7 +228,7 @@ impl ContractInteract { ) { let wallet_addresses = self.wallet_addresses.clone(); let contract_addresses = self.config.contract_addresses.clone(); - let pair_address = self.config.pair_address.clone(); + let pair_address = self.config.general.pair_address.clone(); let fc = self.build_swap_function_call(&want_token_id, want_amount_min); let mut buffer = self.interactor.homogenous_call_buffer(); @@ -287,22 +278,22 @@ impl ContractInteract { } pub async fn swap1_direct(&mut self, wegld_amount: u64, usdc_amount_min: u64) { - let send_token = self.config.wegld_token_id.clone(); - let want_token = self.config.usdc_token_id.clone(); + let send_token = self.config.general.wegld_token_id.clone(); + let want_token = self.config.general.usdc_token_id.clone(); self.swap_direct_impl(send_token, wegld_amount, want_token, usdc_amount_min) .await; } pub async fn swap2_direct(&mut self, usdc_amount: u64, wegld_amount_min: u64) { - let send_token = self.config.usdc_token_id.clone(); - let want_token = self.config.wegld_token_id.clone(); + let send_token = self.config.general.usdc_token_id.clone(); + let want_token = self.config.general.wegld_token_id.clone(); self.swap_direct_impl(send_token, usdc_amount, want_token, wegld_amount_min) .await; } pub async fn swap1_sync(&mut self, wegld_amount: u64, usdc_amount_min: u64) { - let send_token = self.config.wegld_token_id.clone(); - let want_token = self.config.usdc_token_id.clone(); + let send_token = self.config.general.wegld_token_id.clone(); + let want_token = self.config.general.usdc_token_id.clone(); self.swap_via_forwarder_impl( send_token, wegld_amount, @@ -314,8 +305,8 @@ impl ContractInteract { } pub async fn swap2_sync(&mut self, usdc_amount: u64, wegld_amount_min: u64) { - let send_token = self.config.usdc_token_id.clone(); - let want_token = self.config.wegld_token_id.clone(); + let send_token = self.config.general.usdc_token_id.clone(); + let want_token = self.config.general.wegld_token_id.clone(); self.swap_via_forwarder_impl( send_token, usdc_amount, @@ -327,8 +318,8 @@ impl ContractInteract { } pub async fn swap1_async1(&mut self, wegld_amount: u64, usdc_amount_min: u64) { - let send_token = self.config.wegld_token_id.clone(); - let want_token = self.config.usdc_token_id.clone(); + let send_token = self.config.general.wegld_token_id.clone(); + let want_token = self.config.general.usdc_token_id.clone(); self.swap_via_forwarder_impl( send_token, wegld_amount, @@ -340,8 +331,8 @@ impl ContractInteract { } pub async fn swap2_async1(&mut self, usdc_amount: u64, wegld_amount_min: u64) { - let send_token = self.config.usdc_token_id.clone(); - let want_token = self.config.wegld_token_id.clone(); + let send_token = self.config.general.usdc_token_id.clone(); + let want_token = self.config.general.wegld_token_id.clone(); self.swap_via_forwarder_impl( send_token, usdc_amount, @@ -353,8 +344,8 @@ impl ContractInteract { } pub async fn swap1_async2(&mut self, wegld_amount: u64, usdc_amount_min: u64) { - let send_token = self.config.wegld_token_id.clone(); - let want_token = self.config.usdc_token_id.clone(); + let send_token = self.config.general.wegld_token_id.clone(); + let want_token = self.config.general.usdc_token_id.clone(); self.swap_via_forwarder_impl( send_token, wegld_amount, @@ -366,8 +357,8 @@ impl ContractInteract { } pub async fn swap2_async2(&mut self, usdc_amount: u64, wegld_amount_min: u64) { - let send_token = self.config.usdc_token_id.clone(); - let want_token = self.config.wegld_token_id.clone(); + let send_token = self.config.general.usdc_token_id.clone(); + let want_token = self.config.general.wegld_token_id.clone(); self.swap_via_forwarder_impl( send_token, usdc_amount, @@ -379,8 +370,8 @@ impl ContractInteract { } pub async fn swap1_te(&mut self, wegld_amount: u64, usdc_amount_min: u64) { - let send_token = self.config.wegld_token_id.clone(); - let want_token = self.config.usdc_token_id.clone(); + let send_token = self.config.general.wegld_token_id.clone(); + let want_token = self.config.general.usdc_token_id.clone(); self.swap_via_forwarder_impl( send_token, wegld_amount, @@ -392,8 +383,8 @@ impl ContractInteract { } pub async fn swap2_te(&mut self, usdc_amount: u64, wegld_amount_min: u64) { - let send_token = self.config.usdc_token_id.clone(); - let want_token = self.config.wegld_token_id.clone(); + let send_token = self.config.general.usdc_token_id.clone(); + let want_token = self.config.general.wegld_token_id.clone(); self.swap_via_forwarder_impl( send_token, usdc_amount, @@ -406,8 +397,8 @@ impl ContractInteract { pub async fn drain(&mut self) { let contract_addresses = self.config.contract_addresses.clone(); - let wegld_token_id = self.config.wegld_token_id.clone(); - let usdc_token_id = self.config.usdc_token_id.clone(); + let wegld_token_id = self.config.general.wegld_token_id.clone(); + let usdc_token_id = self.config.general.usdc_token_id.clone(); // For each contract, fetch its on-chain owner and check if we have it registered. let mut owner_per_contract: Vec<(Bech32Address, Bech32Address)> = Vec::new(); @@ -471,10 +462,10 @@ impl ContractInteract { let amount_out = self .interactor .query() - .to(&self.config.pair_address) + .to(&self.config.general.pair_address) .typed(pair_proxy::PairProxy) .get_amount_out_view( - EsdtTokenIdentifier::from(self.config.wegld_token_id.as_str()), + EsdtTokenIdentifier::from(self.config.general.wegld_token_id.as_str()), BigUint::from(wegld_amount), ) .returns(ReturnsResultUnmanaged) @@ -483,7 +474,7 @@ impl ContractInteract { println!( "{wegld_amount} {} -> {} {}", - self.config.wegld_token_id, amount_out, self.config.usdc_token_id + self.config.general.wegld_token_id, amount_out, self.config.general.usdc_token_id ); } @@ -491,7 +482,7 @@ impl ContractInteract { let (wegld_reserve, usdc_reserve, lp_supply) = self .interactor .query() - .to(&self.config.pair_address) + .to(&self.config.general.pair_address) .typed(pair_proxy::PairProxy) .get_reserves_and_total_supply() .returns(ReturnsResultUnmanaged) @@ -499,14 +490,20 @@ impl ContractInteract { .await .into_tuple(); - println!("{} reserve: {wegld_reserve}", self.config.wegld_token_id); - println!("{} reserve: {usdc_reserve}", self.config.usdc_token_id); + println!( + "{} reserve: {wegld_reserve}", + self.config.general.wegld_token_id + ); + println!( + "{} reserve: {usdc_reserve}", + self.config.general.usdc_token_id + ); println!("LP token supply: {lp_supply}"); } pub async fn balances(&mut self) { - let wegld_token_id = self.config.wegld_token_id.clone(); - let usdc_token_id = self.config.usdc_token_id.clone(); + let wegld_token_id = self.config.general.wegld_token_id.clone(); + let usdc_token_id = self.config.general.usdc_token_id.clone(); println!("=== Wallet Balances ==="); for wallet in &self.wallet_addresses.clone() { diff --git a/contracts/feature-tests/composability/forwarder-blind/meta/Cargo.toml b/contracts/feature-tests/composability/forwarder-blind/meta/Cargo.toml index 49be17d8c3..7139305c71 100644 --- a/contracts/feature-tests/composability/forwarder-blind/meta/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-blind/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-interactor/Cargo.toml b/contracts/feature-tests/composability/forwarder-interactor/Cargo.toml index 9e4c14c052..db6eab0c32 100644 --- a/contracts/feature-tests/composability/forwarder-interactor/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-interactor/Cargo.toml @@ -16,12 +16,10 @@ path = "src/interact.rs" path = "../forwarder" [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/contracts/feature-tests/composability/forwarder-interactor/config.toml b/contracts/feature-tests/composability/forwarder-interactor/config.toml index eb77c86462..0fbb6abd39 100644 --- a/contracts/feature-tests/composability/forwarder-interactor/config.toml +++ b/contracts/feature-tests/composability/forwarder-interactor/config.toml @@ -1,7 +1,11 @@ +[connection] +chain_type = 'real' +gateway_uri = 'https://testnet-gateway.multiversx.com' + +[wallet] +test_wallet = 'alice' +# [connection] # chain_type = 'simulator' # gateway_uri = 'http://localhost:8085' -chain_type = 'real' -gateway_uri = 'https://testnet-gateway.multiversx.com' - diff --git a/contracts/feature-tests/composability/forwarder-interactor/src/config.rs b/contracts/feature-tests/composability/forwarder-interactor/src/config.rs deleted file mode 100644 index 4c4f034bb4..0000000000 --- a/contracts/feature-tests/composability/forwarder-interactor/src/config.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![allow(unused)] - -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Contract Interact configuration -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Default for Config { - fn default() -> Self { - Self::new() - } -} - -impl Config { - // Deserializes config from file - pub fn new() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/feature-tests/composability/forwarder-interactor/src/interact.rs b/contracts/feature-tests/composability/forwarder-interactor/src/interact.rs index 95c73fd126..43c144b45f 100644 --- a/contracts/feature-tests/composability/forwarder-interactor/src/interact.rs +++ b/contracts/feature-tests/composability/forwarder-interactor/src/interact.rs @@ -1,18 +1,10 @@ #![allow(non_snake_case)] -mod config; mod proxy; -pub use config::Config; use multiversx_sc_snippets::imports::*; pub use proxy::Color; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; -const STATE_FILE: &str = "state.toml"; pub const FORWARDER_DEPLOY_INTERACTOR_TRACE_PATH: &str = "scenarios/forwarder_deploy_scenario.scen.json"; pub const FORWARDER_BUILTIN_INTERACTOR_TRACE_PATH: &str = @@ -30,7 +22,7 @@ pub async fn forwarder_cli() { let mut args = std::env::args(); let _ = args.next(); let cmd = args.next().expect("at least one argument required"); - let mut interact = ContractInteract::new(Config::new(), None).await; + let mut interact = ContractInteract::new(None).await; match cmd.as_str() { "deploy" => interact.deploy().await, "send_egld" => interact.send_egld().await, @@ -129,24 +121,29 @@ pub async fn forwarder_cli() { } } -#[derive(Debug, Default, Serialize, Deserialize)] +/// Contract Interact configuration +#[derive(Debug, serde::Deserialize)] +pub struct Config { + pub connection: ConnectionConfig, + pub wallet: WalletConfig, +} + +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + vec![self.wallet.wallet().clone()] + } +} + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub struct State { contract_address: Option, } impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - /// Sets the contract address pub fn set_address(&mut self, address: Bech32Address) { self.contract_address = Some(address); @@ -160,49 +157,31 @@ impl State { } } -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} - pub struct ContractInteract { - interactor: Interactor, - pub wallet_address: Address, - contract_code: BytesValue, - pub state: State, + pub interactor: Interactor, + pub wallet_address: Bech32Address, + pub contract_code: BytesValue, + pub state: AutoSave, } impl ContractInteract { - pub async fn new(config: Config, trace_path: Option<&str>) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()); + pub async fn new(trace_path: Option<&str>) -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); if let Some(path) = trace_path { interactor = interactor.with_tracer(path).await; } - interactor.set_current_dir_from_workspace("forwarder-interactor"); - let wallet_address = interactor.register_wallet(test_wallets::alice()).await; - - // Useful in the chain simulator setting - // generate blocks until ESDTSystemSCAddress is enabled - interactor.generate_blocks_until_all_activations().await; - - let contract_code = BytesValue::interpret_from( - "mxsc:../forwarder/output/forwarder.mxsc.json", - &InterpreterContext::default(), - ); - - ContractInteract { + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + Self { interactor, - wallet_address, - contract_code, - state: State::load_state(), + wallet_address: config.wallet.address(), + contract_code: BytesValue::interpret_from( + "mxsc:../forwarder/output/forwarder.mxsc.json", + &InterpreterContext::default(), + ), + state, } } diff --git a/contracts/feature-tests/composability/forwarder-interactor/tests/interact_cs_tests.rs b/contracts/feature-tests/composability/forwarder-interactor/tests/interact_cs_tests.rs index 5e1fa29ee6..5fb1ca32e0 100644 --- a/contracts/feature-tests/composability/forwarder-interactor/tests/interact_cs_tests.rs +++ b/contracts/feature-tests/composability/forwarder-interactor/tests/interact_cs_tests.rs @@ -2,6 +2,31 @@ use forwarder_interact::{Config, ContractInteract}; use multiversx_sc_snippets::imports::*; use serial_test::serial; +fn chain_simulator_config() -> Config { + Config { + connection: ConnectionConfig::chain_simulator(), + wallet: WalletConfig::from_test_wallet("alice"), + } +} + +async fn cs_interactor() -> ContractInteract { + let config = chain_simulator_config(); + let interactor = HttpInteractor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&config) + .await; + let wallet_address = config.wallet.address(); + ContractInteract { + interactor, + wallet_address, + contract_code: BytesValue::interpret_from( + "mxsc:../forwarder/output/forwarder.mxsc.json", + &InterpreterContext::default(), + ), + state: AutoSave::no_save_default(), + } +} + // Simple deploy test that runs using the chain simulator configuration. // In order for this test to work, make sure that the `config.toml` file contains the chain simulator config (or choose it manually) // The chain simulator should already be installed and running before attempting to run this test. @@ -11,7 +36,7 @@ use serial_test::serial; #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn deploy_test_forwarder_cs() { - let mut interactor = ContractInteract::new(Config::chain_simulator_config(), None).await; + let mut interactor = cs_interactor().await; interactor.deploy().await; } diff --git a/contracts/feature-tests/composability/forwarder-interactor/tests/interact_tests.rs b/contracts/feature-tests/composability/forwarder-interactor/tests/interact_tests.rs index 74c4cdb0aa..2886f147a5 100644 --- a/contracts/feature-tests/composability/forwarder-interactor/tests/interact_tests.rs +++ b/contracts/feature-tests/composability/forwarder-interactor/tests/interact_tests.rs @@ -1,4 +1,4 @@ -use forwarder_interact::{Color, Config, ContractInteract}; +use forwarder_interact::{Color, ContractInteract}; use multiversx_sc_snippets::imports::*; const ISSUE_COST: u64 = 50000000000000000u64; @@ -9,7 +9,7 @@ const ISSUE_COST: u64 = 50000000000000000u64; #[tokio::test] #[ignore = "run on demand, relies on real blockchain state"] async fn deploy_test_forwarder() { - let mut interactor = ContractInteract::new(Config::new(), None).await; + let mut interactor = ContractInteract::new(None).await; interactor.deploy().await; } @@ -17,7 +17,7 @@ async fn deploy_test_forwarder() { #[tokio::test] #[ignore = "run on demand"] async fn builtin_func_tokens_test() { - let mut interact = ContractInteract::new(Config::new(), None).await; + let mut interact = ContractInteract::new(None).await; // deploy forwarder interact.deploy().await; @@ -172,7 +172,7 @@ async fn builtin_func_tokens_test() { #[tokio::test] #[ignore = "run on demand"] async fn change_to_dynamic_test() { - let mut interact = ContractInteract::new(Config::new(), None).await; + let mut interact = ContractInteract::new(None).await; // deploy forwarder interact.deploy().await; @@ -226,7 +226,7 @@ async fn change_to_dynamic_test() { #[tokio::test] #[ignore = "run on demand"] async fn update_token_test() { - let mut interact = ContractInteract::new(Config::new(), None).await; + let mut interact = ContractInteract::new(None).await; // deploy forwarder interact.deploy().await; @@ -252,12 +252,12 @@ async fn update_token_test() { #[tokio::test] #[ignore = "run on demand"] async fn modify_creator() { - let mut interact = ContractInteract::new(Config::new(), None).await; + let mut interact = ContractInteract::new(None).await; // deploy forwarder interact.deploy().await; - let wallet_address = interact.wallet_address.clone(); + let wallet_address = interact.wallet_address.to_address(); let sc_address = interact.state.current_address().clone(); // issue dynamic NFT diff --git a/contracts/feature-tests/composability/forwarder-legacy/Cargo.toml b/contracts/feature-tests/composability/forwarder-legacy/Cargo.toml index ad6bc4d1e9..9dca8c38b2 100644 --- a/contracts/feature-tests/composability/forwarder-legacy/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-legacy/Cargo.toml @@ -12,10 +12,8 @@ path = "src/forwarder_legacy_main.rs" path = "../vault" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-legacy/meta/Cargo.toml b/contracts/feature-tests/composability/forwarder-legacy/meta/Cargo.toml index ccd3a77fa2..3e51b40844 100644 --- a/contracts/feature-tests/composability/forwarder-legacy/meta/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-legacy/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-legacy/src/fwd_deploy_legacy.rs b/contracts/feature-tests/composability/forwarder-legacy/src/fwd_deploy_legacy.rs index d573b45ec0..64a58ae0da 100644 --- a/contracts/feature-tests/composability/forwarder-legacy/src/fwd_deploy_legacy.rs +++ b/contracts/feature-tests/composability/forwarder-legacy/src/fwd_deploy_legacy.rs @@ -38,7 +38,7 @@ pub trait DeployContractModule { ) -> (ManagedAddress, OptionalValue) { self.vault_proxy() .init(opt_arg) - .deploy_contract(code, CodeMetadata::DEFAULT) + .deploy_contract(code, CodeMetadata::EMPTY) } #[endpoint] @@ -49,7 +49,7 @@ pub trait DeployContractModule { ) -> MultiValue2> { self.vault_proxy() .init(opt_arg) - .deploy_from_source(&source_address, CodeMetadata::DEFAULT) + .deploy_from_source(&source_address, CodeMetadata::EMPTY) .into() } } diff --git a/contracts/feature-tests/composability/forwarder-raw/Cargo.toml b/contracts/feature-tests/composability/forwarder-raw/Cargo.toml index 03fe6a1b32..5099ffca87 100644 --- a/contracts/feature-tests/composability/forwarder-raw/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-raw/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/forwarder_raw.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/forwarder-raw/meta/Cargo.toml b/contracts/feature-tests/composability/forwarder-raw/meta/Cargo.toml index 472435fc65..3c06deb621 100644 --- a/contracts/feature-tests/composability/forwarder-raw/meta/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder-raw/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/forwarder/Cargo.toml b/contracts/feature-tests/composability/forwarder/Cargo.toml index f8d05b2064..bd5e8465db 100644 --- a/contracts/feature-tests/composability/forwarder/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/forwarder_main.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/forwarder/meta/Cargo.toml b/contracts/feature-tests/composability/forwarder/meta/Cargo.toml index 8a551199e0..81ba03e2c4 100644 --- a/contracts/feature-tests/composability/forwarder/meta/Cargo.toml +++ b/contracts/feature-tests/composability/forwarder/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/forwarder/src/fwd_deploy.rs b/contracts/feature-tests/composability/forwarder/src/fwd_deploy.rs index 7145f2555a..bc8db840bd 100644 --- a/contracts/feature-tests/composability/forwarder/src/fwd_deploy.rs +++ b/contracts/feature-tests/composability/forwarder/src/fwd_deploy.rs @@ -53,7 +53,7 @@ pub trait DeployContractModule { self.tx() .typed(vault_proxy::VaultProxy) .init(opt_arg) - .code_metadata(CodeMetadata::DEFAULT) + .code_metadata(CodeMetadata::EMPTY) .from_source(source_address) .returns(ReturnsNewManagedAddress) .returns(ReturnsResult) diff --git a/contracts/feature-tests/composability/local-esdt-and-nft/Cargo.toml b/contracts/feature-tests/composability/local-esdt-and-nft/Cargo.toml index b9841fb441..cb73378deb 100644 --- a/contracts/feature-tests/composability/local-esdt-and-nft/Cargo.toml +++ b/contracts/feature-tests/composability/local-esdt-and-nft/Cargo.toml @@ -10,9 +10,7 @@ path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/local-esdt-and-nft/meta/Cargo.toml b/contracts/feature-tests/composability/local-esdt-and-nft/meta/Cargo.toml index ed5736a1ad..185ca79d22 100644 --- a/contracts/feature-tests/composability/local-esdt-and-nft/meta/Cargo.toml +++ b/contracts/feature-tests/composability/local-esdt-and-nft/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/mesh-interactor/Cargo.toml b/contracts/feature-tests/composability/mesh-interactor/Cargo.toml index 7e27d28e43..6afae0b343 100644 --- a/contracts/feature-tests/composability/mesh-interactor/Cargo.toml +++ b/contracts/feature-tests/composability/mesh-interactor/Cargo.toml @@ -16,12 +16,10 @@ tokio = { version = "1.24" } toml = "1.0" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies.mesh-node] path = "../mesh-node" diff --git a/contracts/feature-tests/composability/mesh-interactor/config.toml b/contracts/feature-tests/composability/mesh-interactor/config.toml index 01071db10c..fc951c962e 100644 --- a/contracts/feature-tests/composability/mesh-interactor/config.toml +++ b/contracts/feature-tests/composability/mesh-interactor/config.toml @@ -1,9 +1,11 @@ -call_tree_path = "layouts/async_v1_c1_smin_pay.toml" - -[gateway] -uri = "https://testnet-gateway.multiversx.com/" +[connection] +gateway_uri = "https://testnet-gateway.multiversx.com/" chain_type = "real" -# [gateway] -# uri = "https://gateway.battleofnodes.com" +# [connection] +# gateway_uri = "https://gateway.battleofnodes.com" # chain_type = "real" + +[general] +contract_path = "../mesh-node/output/mesh-node.mxsc.json" +call_tree_path = "layouts/async_v1_c1_smin_pay.toml" diff --git a/contracts/feature-tests/composability/mesh-interactor/src/call_tree_config.rs b/contracts/feature-tests/composability/mesh-interactor/src/call_tree_config.rs index 0645b67859..5850ebee90 100644 --- a/contracts/feature-tests/composability/mesh-interactor/src/call_tree_config.rs +++ b/contracts/feature-tests/composability/mesh-interactor/src/call_tree_config.rs @@ -5,89 +5,29 @@ use std::{ }; use multiversx_sc::types::ShardId; +use multiversx_sc_snippets::imports::*; use serde::{Deserialize, Serialize}; -/// Top-level config file path. -pub const CONFIG_FILE: &str = "config.toml"; - -/// Default call tree layout file path (used when `config.toml` is absent). -pub const CALL_TREE_FILE: &str = "call_tree.toml"; - /// Call tree state file (deployed addresses), always `current.toml`. pub const STATE_FILE: &str = "current.toml"; -/// Chain type for the gateway connection. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Gateway connection settings. -#[derive(Debug, Serialize, Deserialize)] -pub struct GatewayConfig { - pub uri: String, - pub chain_type: ChainType, -} - -impl Default for GatewayConfig { - fn default() -> Self { - GatewayConfig { - uri: "https://testnet-gateway.multiversx.com".to_string(), - chain_type: ChainType::Real, - } - } -} - -impl GatewayConfig { - pub fn use_chain_simulator(&self) -> bool { - self.chain_type == ChainType::Simulator - } +/// General settings for the mesh interact. +#[derive(Debug, Deserialize)] +pub struct GeneralConfig { + pub contract_path: ConfigPath, + pub call_tree_path: ConfigPath, } /// Top-level interactor config, stored in `config.toml`. -/// -/// Contains the gateway settings and the path to the call tree layout file. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] pub struct InteractConfig { - pub gateway: GatewayConfig, - /// Path to the call tree layout TOML (defaults to `"call_tree.toml"`). - #[serde(default = "default_call_tree_path")] - pub call_tree_path: String, + pub connection: ConnectionConfig, + pub general: GeneralConfig, } -fn default_call_tree_path() -> String { - CALL_TREE_FILE.to_string() -} - -impl Default for InteractConfig { - fn default() -> Self { - InteractConfig { - gateway: GatewayConfig::default(), - call_tree_path: CALL_TREE_FILE.to_string(), - } - } -} - -impl InteractConfig { - pub fn load_from_file(path: &str) -> Self { - if !Path::new(path).exists() { - return Self::default(); - } - let mut file = std::fs::File::open(path).expect("failed to open config file"); - let mut content = String::new(); - file.read_to_string(&mut content) - .expect("failed to read config file"); - toml::from_str(&content).expect("failed to deserialize config") - } - - #[allow(dead_code)] - pub fn save_to_file(&self, path: &str) { - let toml_str = toml::to_string_pretty(self).expect("failed to serialize config"); - let mut file = std::fs::File::create(path).expect("failed to create config file"); - file.write_all(toml_str.as_bytes()) - .expect("failed to write config file"); +impl InteractorConfig for InteractConfig { + fn connection(&self) -> &ConnectionConfig { + &self.connection } } @@ -214,7 +154,7 @@ pub struct CallTreeLayout { impl CallTreeLayout { /// Serialize the call tree layout to a TOML file. - pub fn save_to_file(&self, path: &str) { + pub fn save_to_file(&self, path: impl AsRef) { const HEADER: &str = "# Code generated by mesh-interactor. DO NOT EDIT.\n\n"; let toml_str = toml::to_string_pretty(self).expect("failed to serialize call tree layout"); let mut file = std::fs::File::create(path).expect("failed to create call tree layout file"); @@ -225,7 +165,7 @@ impl CallTreeLayout { } /// Load a call tree layout from a TOML file. - pub fn load_from_file(path: &str) -> Self { + pub fn load_from_file(path: impl AsRef) -> Self { let mut file = std::fs::File::open(path).expect("failed to open call tree layout file"); let mut content = String::new(); file.read_to_string(&mut content) diff --git a/contracts/feature-tests/composability/mesh-interactor/src/call_tree_deploy.rs b/contracts/feature-tests/composability/mesh-interactor/src/call_tree_deploy.rs index 1843afc9f7..640d11cf21 100644 --- a/contracts/feature-tests/composability/mesh-interactor/src/call_tree_deploy.rs +++ b/contracts/feature-tests/composability/mesh-interactor/src/call_tree_deploy.rs @@ -44,7 +44,7 @@ impl ComposabilityInteract { let code_metadata = if contract.payable.unwrap_or(false) { CodeMetadata::PAYABLE } else { - CodeMetadata::DEFAULT + CodeMetadata::EMPTY }; buffer.push_tx(|tx| { tx.from(wallet) diff --git a/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_controller.rs b/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_controller.rs index 7884fbb140..10f933523e 100644 --- a/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_controller.rs +++ b/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_controller.rs @@ -1,4 +1,4 @@ -use crate::call_tree_config::{CONFIG_FILE, InteractConfig}; +use crate::call_tree_config::InteractConfig; use multiversx_sc_snippets::imports::*; @@ -11,14 +11,8 @@ pub struct ComposabilityInteract { impl ComposabilityInteract { pub async fn init() -> Self { - let config = InteractConfig::load_from_file(CONFIG_FILE); - let gateway_config = &config.gateway; - let mut interactor = Interactor::new(&gateway_config.uri) - .await - .use_chain_simulator(gateway_config.use_chain_simulator()); - interactor.set_current_dir_from_workspace( - "contracts/feature-tests/composability/mesh-interactor", - ); + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: InteractConfig = interactor.load_config_toml().await; let shard_wallet_addresses = [ interactor .register_wallet(test_wallets::for_shard(0u32.into())) @@ -31,7 +25,7 @@ impl ComposabilityInteract { .await, ]; let forw_queue_code = BytesValue::interpret_from( - "mxsc:../mesh-node/output/mesh-node.mxsc.json", + format!("mxsc:{}", config.general.contract_path), &InterpreterContext::default(), ); diff --git a/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_main.rs b/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_main.rs index 874bb16fe0..406b71b33a 100644 --- a/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_main.rs +++ b/contracts/feature-tests/composability/mesh-interactor/src/mesh_interact_main.rs @@ -16,12 +16,12 @@ use multiversx_sc_snippets::imports::*; impl ComposabilityInteract { fn load_layout(&self) -> CallTreeLayout { - CallTreeLayout::load_from_file(&self.config.call_tree_path) + CallTreeLayout::load_from_file(&self.config.general.call_tree_path) } async fn cmd_update_gas(&mut self) { println!("Updating gas estimates..."); - let layout_path = self.config.call_tree_path.clone(); + let layout_path = self.config.general.call_tree_path.clone(); let mut layout = self.load_layout(); layout.fill_gas_estimates(); layout.save_to_file(&layout_path); diff --git a/contracts/feature-tests/composability/mesh-node/Cargo.toml b/contracts/feature-tests/composability/mesh-node/Cargo.toml index b7fd07792f..878ddc07f1 100644 --- a/contracts/feature-tests/composability/mesh-node/Cargo.toml +++ b/contracts/feature-tests/composability/mesh-node/Cargo.toml @@ -9,8 +9,7 @@ publish = false path = "src/mesh_node.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies.multiversx-sc-wasm-adapter] version = "0.66.2" @@ -18,5 +17,4 @@ path = "../../../../framework/wasm-adapter" optional = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/mesh-node/meta/Cargo.toml b/contracts/feature-tests/composability/mesh-node/meta/Cargo.toml index d9f23a3429..914ef45d8d 100644 --- a/contracts/feature-tests/composability/mesh-node/meta/Cargo.toml +++ b/contracts/feature-tests/composability/mesh-node/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/proxy-test-first/Cargo.toml b/contracts/feature-tests/composability/proxy-test-first/Cargo.toml index 8888d830d1..87643934fd 100644 --- a/contracts/feature-tests/composability/proxy-test-first/Cargo.toml +++ b/contracts/feature-tests/composability/proxy-test-first/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/proxy-test-first.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/proxy-test-first/meta/Cargo.toml b/contracts/feature-tests/composability/proxy-test-first/meta/Cargo.toml index 343633f10e..a72dbecff3 100644 --- a/contracts/feature-tests/composability/proxy-test-first/meta/Cargo.toml +++ b/contracts/feature-tests/composability/proxy-test-first/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/proxy-test-second/Cargo.toml b/contracts/feature-tests/composability/proxy-test-second/Cargo.toml index d9e2c9fcf7..2673f12497 100644 --- a/contracts/feature-tests/composability/proxy-test-second/Cargo.toml +++ b/contracts/feature-tests/composability/proxy-test-second/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/proxy-test-second.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/proxy-test-second/meta/Cargo.toml b/contracts/feature-tests/composability/proxy-test-second/meta/Cargo.toml index 1f5390d304..74af06e44b 100644 --- a/contracts/feature-tests/composability/proxy-test-second/meta/Cargo.toml +++ b/contracts/feature-tests/composability/proxy-test-second/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/recursive-caller/Cargo.toml b/contracts/feature-tests/composability/recursive-caller/Cargo.toml index 9ce3bee4f4..dac4298df1 100644 --- a/contracts/feature-tests/composability/recursive-caller/Cargo.toml +++ b/contracts/feature-tests/composability/recursive-caller/Cargo.toml @@ -12,9 +12,7 @@ path = "src/recursive_caller.rs" path = "../vault" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/recursive-caller/meta/Cargo.toml b/contracts/feature-tests/composability/recursive-caller/meta/Cargo.toml index c05ac1f868..536a77442a 100644 --- a/contracts/feature-tests/composability/recursive-caller/meta/Cargo.toml +++ b/contracts/feature-tests/composability/recursive-caller/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/transfer-role-features/Cargo.toml b/contracts/feature-tests/composability/transfer-role-features/Cargo.toml index 698a158586..23c28d5669 100644 --- a/contracts/feature-tests/composability/transfer-role-features/Cargo.toml +++ b/contracts/feature-tests/composability/transfer-role-features/Cargo.toml @@ -12,13 +12,10 @@ path = "src/lib.rs" path = "../vault" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../../contracts/modules" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/transfer-role-features/meta/Cargo.toml b/contracts/feature-tests/composability/transfer-role-features/meta/Cargo.toml index 0ef8cc40bc..b0b56e2555 100644 --- a/contracts/feature-tests/composability/transfer-role-features/meta/Cargo.toml +++ b/contracts/feature-tests/composability/transfer-role-features/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/composability/vault/Cargo.toml b/contracts/feature-tests/composability/vault/Cargo.toml index 3717d6a65c..b48868d1a4 100644 --- a/contracts/feature-tests/composability/vault/Cargo.toml +++ b/contracts/feature-tests/composability/vault/Cargo.toml @@ -10,9 +10,7 @@ path = "src/vault.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/composability/vault/meta/Cargo.toml b/contracts/feature-tests/composability/vault/meta/Cargo.toml index 034128e231..f4e6040c0e 100644 --- a/contracts/feature-tests/composability/vault/meta/Cargo.toml +++ b/contracts/feature-tests/composability/vault/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/Cargo.toml b/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/Cargo.toml index 90080df945..f1257c50e6 100644 --- a/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/Cargo.toml @@ -12,10 +12,8 @@ path = "src/crowdfunding_erc20.rs" path = "../erc20" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/meta/Cargo.toml index 5c3d0f5983..5990f417f4 100644 --- a/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/crowdfunding-erc20/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/Cargo.toml index 5fbfc8255d..34a068f5b1 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/Cargo.toml @@ -13,10 +13,8 @@ path = "src/lib.rs" path = "../erc1155" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/meta/Cargo.toml index 4f76537f6b..bb461eb9b0 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155-marketplace/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/Cargo.toml index 3c25dda777..bd86ab13a9 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/meta/Cargo.toml index e4ad4e0551..211f28a1ee 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155-user-mock/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc1155/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155/Cargo.toml index 5c357d14d6..91e1b8f4d0 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155/Cargo.toml @@ -9,13 +9,11 @@ publish = false path = "src/erc1155.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true [dev-dependencies.erc1155-user-mock] path="../erc1155-user-mock" diff --git a/contracts/feature-tests/erc-style-contracts/erc1155/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc1155/meta/Cargo.toml index 5352c174ff..d45c7d2407 100644 --- a/contracts/feature-tests/erc-style-contracts/erc1155/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc1155/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc20/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc20/Cargo.toml index d79ebf7f49..4802271f9e 100644 --- a/contracts/feature-tests/erc-style-contracts/erc20/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc20/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/erc20.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc20/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc20/meta/Cargo.toml index f5eae7af27..428e292c85 100644 --- a/contracts/feature-tests/erc-style-contracts/erc20/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc20/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc721/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc721/Cargo.toml index fafe2fd5a1..963e4c4ba3 100644 --- a/contracts/feature-tests/erc-style-contracts/erc721/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc721/Cargo.toml @@ -10,9 +10,7 @@ path = "src/erc721.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/erc721/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/erc721/meta/Cargo.toml index d582e276bc..4b6c0b8d42 100644 --- a/contracts/feature-tests/erc-style-contracts/erc721/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/erc721/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/lottery-erc20/Cargo.toml b/contracts/feature-tests/erc-style-contracts/lottery-erc20/Cargo.toml index b830ff831c..3cd698d3be 100644 --- a/contracts/feature-tests/erc-style-contracts/lottery-erc20/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/lottery-erc20/Cargo.toml @@ -12,10 +12,8 @@ path = "src/lottery.rs" path = "../erc20" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/erc-style-contracts/lottery-erc20/meta/Cargo.toml b/contracts/feature-tests/erc-style-contracts/lottery-erc20/meta/Cargo.toml index a77d41ebbe..4f5ded756c 100644 --- a/contracts/feature-tests/erc-style-contracts/lottery-erc20/meta/Cargo.toml +++ b/contracts/feature-tests/erc-style-contracts/lottery-erc20/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/esdt-system-sc-mock/Cargo.toml b/contracts/feature-tests/esdt-system-sc-mock/Cargo.toml index 88c5357b79..e0bebb4857 100644 --- a/contracts/feature-tests/esdt-system-sc-mock/Cargo.toml +++ b/contracts/feature-tests/esdt-system-sc-mock/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/esdt_system_sc_mock.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/esdt-system-sc-mock/meta/Cargo.toml b/contracts/feature-tests/esdt-system-sc-mock/meta/Cargo.toml index 88019fcade..cecbc0c878 100644 --- a/contracts/feature-tests/esdt-system-sc-mock/meta/Cargo.toml +++ b/contracts/feature-tests/esdt-system-sc-mock/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/exchange-features/Cargo.toml b/contracts/feature-tests/exchange-features/Cargo.toml index 048848856d..84860f9b14 100644 --- a/contracts/feature-tests/exchange-features/Cargo.toml +++ b/contracts/feature-tests/exchange-features/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/exchange_features.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/exchange-features/meta/Cargo.toml b/contracts/feature-tests/exchange-features/meta/Cargo.toml index 9905df5b1f..022b925bf2 100644 --- a/contracts/feature-tests/exchange-features/meta/Cargo.toml +++ b/contracts/feature-tests/exchange-features/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/formatted-message-features/Cargo.toml b/contracts/feature-tests/formatted-message-features/Cargo.toml index ec60e5c62a..08bb1def64 100644 --- a/contracts/feature-tests/formatted-message-features/Cargo.toml +++ b/contracts/feature-tests/formatted-message-features/Cargo.toml @@ -9,11 +9,9 @@ publish = false path = "src/formatted_message_features.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true features = ["alloc"] [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/formatted-message-features/meta/Cargo.toml b/contracts/feature-tests/formatted-message-features/meta/Cargo.toml index 1a7dc69ecf..4d1d65ce5a 100644 --- a/contracts/feature-tests/formatted-message-features/meta/Cargo.toml +++ b/contracts/feature-tests/formatted-message-features/meta/Cargo.toml @@ -11,6 +11,4 @@ authors = ["you"] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/gas-tests/Cargo.lock b/contracts/feature-tests/gas-tests/Cargo.lock index 453ef3737f..f7ec9aad68 100644 --- a/contracts/feature-tests/gas-tests/Cargo.lock +++ b/contracts/feature-tests/gas-tests/Cargo.lock @@ -101,21 +101,21 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -132,6 +132,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.8.3" @@ -150,7 +156,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -160,8 +166,8 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", - "syn 2.0.117", + "shlex 1.3.0", + "syn 2.0.118", ] [[package]] @@ -172,15 +178,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -208,18 +214,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecheck" @@ -263,7 +269,7 @@ checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -274,18 +280,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cc" -version = "1.2.62" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -305,9 +311,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -356,7 +362,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -421,9 +427,9 @@ dependencies = [ [[package]] name = "corosensei" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c54787b605c7df106ceccf798df23da4f2e09918defad66705d1cedf3bb914f" +checksum = "6886a0c0f263965933c438626e7179139a62b978a33aa18281cbf0cd5a975f34" dependencies = [ "autocfg", "cfg-if", @@ -536,7 +542,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -559,7 +565,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -570,7 +576,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -605,7 +611,7 @@ checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -627,7 +633,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", "unicode-xid", ] @@ -648,7 +654,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -695,13 +701,13 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d36219658beb39702975c707dee7895943ca281ca46eebbc5ea395171b9c182b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "byteorder", "lazy_static", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -724,7 +730,7 @@ dependencies = [ "byteorder", "dynasm 4.0.2", "fnv", - "memmap2 0.9.10", + "memmap2 0.9.11", ] [[package]] @@ -801,7 +807,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -870,12 +876,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -933,9 +933,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.4.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" dependencies = [ "rustversion", "typenum", @@ -956,16 +956,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] @@ -1018,22 +1016,13 @@ dependencies = [ "ahash 0.8.12", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] @@ -1075,12 +1064,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1142,13 +1125,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1170,9 +1152,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" [[package]] name = "leb128fmt" @@ -1208,9 +1190,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -1244,9 +1226,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loupe" @@ -1291,9 +1273,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -1315,9 +1297,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -1364,11 +1346,13 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "multiversx-chain-core" -version = "0.22.1" +version = "0.23.1" dependencies = [ + "base64", "bech32", - "bitflags 2.11.1", + "bitflags 2.13.0", "blake2", + "ed25519-dalek", "hex", "multiversx-sc-codec", "serde", @@ -1378,7 +1362,7 @@ dependencies = [ [[package]] name = "multiversx-chain-scenario-format" -version = "0.26.0" +version = "0.26.1" dependencies = [ "bech32", "hex", @@ -1391,12 +1375,11 @@ dependencies = [ [[package]] name = "multiversx-chain-vm" -version = "0.22.1" +version = "0.23.1" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "colored", - "ed25519-dalek", "hex", "itertools 0.14.0", "log", @@ -1462,10 +1445,10 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.65.1" +version = "0.66.2" dependencies = [ - "bitflags 2.11.1", - "generic-array 1.4.1", + "bitflags 2.13.0", + "generic-array 1.4.3", "multiversx-chain-core", "multiversx-sc-codec", "multiversx-sc-derive", @@ -1478,7 +1461,7 @@ name = "multiversx-sc-codec" version = "0.25.0" dependencies = [ "arrayvec", - "bitflags 2.11.1", + "bitflags 2.13.0", "multiversx-sc-codec-derive", "num-bigint", "num-traits", @@ -1492,23 +1475,23 @@ dependencies = [ "hex", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "multiversx-sc-derive" -version = "0.65.1" +version = "0.66.2" dependencies = [ "hex", "proc-macro2", "quote", "radix_trie", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "multiversx-sc-meta-lib" -version = "0.65.1" +version = "0.66.2" dependencies = [ "clap", "colored", @@ -1529,7 +1512,7 @@ dependencies = [ [[package]] name = "multiversx-sc-scenario" -version = "0.65.1" +version = "0.66.2" dependencies = [ "colored", "hex", @@ -1567,7 +1550,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1718,7 +1701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1764,7 +1747,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1813,14 +1796,14 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -1863,7 +1846,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -1923,7 +1906,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -1939,9 +1922,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1962,9 +1945,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "region" @@ -2052,7 +2035,7 @@ checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2082,7 +2065,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2095,7 +2078,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2191,14 +2174,14 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2265,6 +2248,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signature" version = "2.2.0" @@ -2300,9 +2289,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spki" @@ -2357,9 +2346,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -2396,7 +2385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -2437,7 +2426,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2448,7 +2437,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2525,7 +2514,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -2549,9 +2538,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -2561,9 +2550,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -2591,9 +2580,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "js-sys", "wasm-bindgen", @@ -2611,29 +2600,11 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen 0.46.0", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2644,9 +2615,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2654,56 +2625,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69830ccbbf41c55eb585991659fb70867ef628193af3a495f09a6956f7615e59" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.249.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasmparser 0.252.0", ] [[package]] @@ -3042,32 +2991,31 @@ version = "0.224.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f17a5917c2ddd3819e84c661fae0d6ba29d7b9c1f0e96c708c65a9c4188e11" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.249.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", + "bitflags 2.13.0", + "hashbrown 0.17.1", "indexmap 2.14.0", "semver", + "serde", ] [[package]] name = "wasmparser" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.17.1", + "bitflags 2.13.0", "indexmap 2.14.0", "semver", - "serde", ] [[package]] @@ -3083,22 +3031,22 @@ dependencies = [ [[package]] name = "wast" -version = "249.0.0" +version = "252.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2474a321bf9ae2808e9fa23ac4ec2b27300e70985e30bcb5a38d43b76bfc901a" +checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.249.0", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.249.0" +version = "1.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28af699d0a9c7e4e250b7b8e36167ae5215fbb4b7ae526bb4ce7b234ba0afc90" +checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" dependencies = [ "wast", ] @@ -3315,100 +3263,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -3436,29 +3290,29 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zmij" diff --git a/contracts/feature-tests/multi-contract-features/Cargo.toml b/contracts/feature-tests/multi-contract-features/Cargo.toml index 2e58d2f4a8..defd3b90a4 100644 --- a/contracts/feature-tests/multi-contract-features/Cargo.toml +++ b/contracts/feature-tests/multi-contract-features/Cargo.toml @@ -12,9 +12,7 @@ path = "src/multi_contract_features.rs" example_feature = [] [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/multi-contract-features/meta/Cargo.toml b/contracts/feature-tests/multi-contract-features/meta/Cargo.toml index df5d08b4cb..b54f8c442c 100644 --- a/contracts/feature-tests/multi-contract-features/meta/Cargo.toml +++ b/contracts/feature-tests/multi-contract-features/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/panic-message-features/Cargo.toml b/contracts/feature-tests/panic-message-features/Cargo.toml index 9cb262b522..81ece5a907 100644 --- a/contracts/feature-tests/panic-message-features/Cargo.toml +++ b/contracts/feature-tests/panic-message-features/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/panic_features.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/panic-message-features/meta/Cargo.toml b/contracts/feature-tests/panic-message-features/meta/Cargo.toml index fe4b8bc871..0e32d35129 100644 --- a/contracts/feature-tests/panic-message-features/meta/Cargo.toml +++ b/contracts/feature-tests/panic-message-features/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/payable-features/Cargo.toml b/contracts/feature-tests/payable-features/Cargo.toml index f66144b861..f0cf237c0c 100644 --- a/contracts/feature-tests/payable-features/Cargo.toml +++ b/contracts/feature-tests/payable-features/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/payable_features.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/payable-features/interactor/Cargo.toml b/contracts/feature-tests/payable-features/interactor/Cargo.toml index ce1b6f87cb..341b9c29f3 100644 --- a/contracts/feature-tests/payable-features/interactor/Cargo.toml +++ b/contracts/feature-tests/payable-features/interactor/Cargo.toml @@ -16,8 +16,7 @@ path = "src/payable_interactor.rs" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/contracts/feature-tests/payable-features/interactor/config.toml b/contracts/feature-tests/payable-features/interactor/config.toml index 4a108d868b..cafbb342e1 100644 --- a/contracts/feature-tests/payable-features/interactor/config.toml +++ b/contracts/feature-tests/payable-features/interactor/config.toml @@ -1,6 +1,12 @@ - +[connection] # chain_type = 'simulator' # gateway_uri = 'http://localhost:8085' chain_type = 'real' -gateway_uri = 'https://testnet-gateway.multiversx.com' +gateway_uri = 'https://devnet-gateway.multiversx.com' + +[owner] +test_wallet = 'heidi' + +[wallet] +test_wallet = 'ivan' diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs index 673cd847ef..fd3ba52a76 100644 --- a/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs +++ b/contracts/feature-tests/payable-features/interactor/src/payable_interactor.rs @@ -1,22 +1,49 @@ mod payable_interactor_cli; -mod payable_interactor_config; -mod payable_interactor_state; use clap::Parser; +use multiversx_sc_snippets::imports::*; use payable_features::payable_features_proxy; -pub use payable_interactor_config::Config; -use payable_interactor_state::State; +use serde::{Deserialize, Serialize}; + +/// Payable Features Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub connection: ConnectionConfig, + pub owner: WalletConfig, + pub wallet: WalletConfig, +} -use multiversx_sc_snippets::imports::*; +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + vec![self.owner.wallet().clone(), self.wallet.wallet().clone()] + } +} + +/// Payable Features Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + pub payable_features_address: Option, +} + +impl State { + /// Returns the payable features contract + pub fn current_payable_features_address(&self) -> &Bech32Address { + self.payable_features_address + .as_ref() + .expect("no known payable features contract, deploy first") + } +} const CODE_PATH: MxscPath = MxscPath::new("../output/payable-features.mxsc.json"); pub async fn payable_features_cli() { env_logger::init(); - let config = Config::load_config(); - - let mut payable_interact = PayableInteract::new(config).await; + let mut payable_interact = PayableInteract::new().await; let cli = payable_interactor_cli::InteractCli::parse(); match &cli.command { @@ -37,38 +64,28 @@ pub async fn payable_features_cli() { pub struct PayableInteract { pub interactor: Interactor, - pub sc_owner_address: Bech32Address, - pub wallet_address: Bech32Address, - pub state: State, + pub config: Config, + pub state: AutoSave, } impl PayableInteract { - pub async fn new(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()); - - interactor - .set_current_dir_from_workspace("contracts/feature-tests/payable-features/interactor"); - - let sc_owner_address = interactor.register_wallet(test_wallets::heidi()).await; - let wallet_address = interactor.register_wallet(test_wallets::ivan()).await; - - interactor.generate_blocks(30u64).await.unwrap(); - - PayableInteract { + pub async fn new() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + Self { interactor, - sc_owner_address: sc_owner_address.into(), - wallet_address: wallet_address.into(), - state: State::load_state(), + config, + state, } } pub async fn deploy(&mut self) { + let owner_address = self.config.owner.address(); let new_address = self .interactor .tx() - .from(&self.sc_owner_address.clone()) + .from(&owner_address.clone()) .gas(30_000_000) .typed(payable_features_proxy::PayableFeaturesProxy) .init() @@ -79,7 +96,7 @@ impl PayableInteract { .await; println!("new address: {new_address}"); - self.state.set_payable_features_address(new_address); + self.state.payable_features_address = Some(new_address); } pub async fn check_multi_transfer_only_egld_transfer(&mut self) { @@ -89,7 +106,7 @@ impl PayableInteract { let result = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_payable_features_address()) .gas(6_000_000u64) .typed(payable_features_proxy::PayableFeaturesProxy) @@ -110,7 +127,7 @@ impl PayableInteract { let result = self .interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_payable_features_address()) .gas(6_000_000u64) .typed(payable_features_proxy::PayableFeaturesProxy) diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs deleted file mode 100644 index bd19da629d..0000000000 --- a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_config.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -/// Adder Interact configuration -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs b/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs deleted file mode 100644 index c5ed95f47d..0000000000 --- a/contracts/feature-tests/payable-features/interactor/src/payable_interactor_state.rs +++ /dev/null @@ -1,50 +0,0 @@ -use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; - -/// Payable Features Interact state -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - payable_features_address: Option, -} - -impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - /// Sets the payable features address - pub fn set_payable_features_address(&mut self, address: Bech32Address) { - self.payable_features_address = Some(address); - } - - /// Returns the payable features contract - pub fn current_payable_features_address(&self) -> &Bech32Address { - self.payable_features_address - .as_ref() - .expect("no known payable features contract, deploy first") - } -} - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/feature-tests/payable-features/interactor/tests/payable_interactor_cs_test.rs b/contracts/feature-tests/payable-features/interactor/tests/payable_interactor_cs_test.rs index 87a02a511b..623cfe6909 100644 --- a/contracts/feature-tests/payable-features/interactor/tests/payable_interactor_cs_test.rs +++ b/contracts/feature-tests/payable-features/interactor/tests/payable_interactor_cs_test.rs @@ -1,11 +1,33 @@ +use multiversx_sc_snippets::imports::*; use payable_interactor::{Config, PayableInteract}; use serial_test::serial; +fn chain_simulator_config() -> Config { + Config { + connection: ConnectionConfig::chain_simulator(), + owner: WalletConfig::from_test_wallet("mike"), + wallet: WalletConfig::from_test_wallet("ivan"), + } +} + +async fn cs_interactor() -> PayableInteract { + let config = chain_simulator_config(); + let interactor = HttpInteractor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&config) + .await; + PayableInteract { + interactor, + config, + state: AutoSave::no_save_default(), + } +} + #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn payable_interactor_test() { - let mut payable_interact = PayableInteract::new(Config::chain_simulator_config()).await; + let mut payable_interact = cs_interactor().await; payable_interact.deploy().await; diff --git a/contracts/feature-tests/payable-features/meta/Cargo.toml b/contracts/feature-tests/payable-features/meta/Cargo.toml index 1e4783f523..58ac86c4f6 100644 --- a/contracts/feature-tests/payable-features/meta/Cargo.toml +++ b/contracts/feature-tests/payable-features/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/rust-snippets-generator-test/Cargo.toml b/contracts/feature-tests/rust-snippets-generator-test/Cargo.toml index 5854c501e1..3c3e48eda0 100644 --- a/contracts/feature-tests/rust-snippets-generator-test/Cargo.toml +++ b/contracts/feature-tests/rust-snippets-generator-test/Cargo.toml @@ -9,9 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true diff --git a/contracts/feature-tests/rust-snippets-generator-test/interact-rs/Cargo.toml b/contracts/feature-tests/rust-snippets-generator-test/interact-rs/Cargo.toml index 97777d0dbe..8ae32f16d3 100644 --- a/contracts/feature-tests/rust-snippets-generator-test/interact-rs/Cargo.toml +++ b/contracts/feature-tests/rust-snippets-generator-test/interact-rs/Cargo.toml @@ -13,8 +13,7 @@ path = "src/interactor_main.rs" path = ".." [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true # [workspace] diff --git a/contracts/feature-tests/rust-snippets-generator-test/meta/Cargo.toml b/contracts/feature-tests/rust-snippets-generator-test/meta/Cargo.toml index 8df110e7dc..db45479bac 100644 --- a/contracts/feature-tests/rust-snippets-generator-test/meta/Cargo.toml +++ b/contracts/feature-tests/rust-snippets-generator-test/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/rust-testing-framework-tester/Cargo.toml b/contracts/feature-tests/rust-testing-framework-tester/Cargo.toml index 29f2b88b2d..692b4fcf70 100644 --- a/contracts/feature-tests/rust-testing-framework-tester/Cargo.toml +++ b/contracts/feature-tests/rust-testing-framework-tester/Cargo.toml @@ -6,8 +6,7 @@ edition = "2024" publish = false [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true features = [ "alloc" ] [dev-dependencies.adder] @@ -17,8 +16,7 @@ path = "../../examples/adder" path = "../../feature-tests/basic-features" [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true [dev-dependencies] num-bigint = "0.4" diff --git a/contracts/feature-tests/rust-testing-framework-tester/meta/Cargo.toml b/contracts/feature-tests/rust-testing-framework-tester/meta/Cargo.toml index bf7b1f00e8..9556226b4f 100644 --- a/contracts/feature-tests/rust-testing-framework-tester/meta/Cargo.toml +++ b/contracts/feature-tests/rust-testing-framework-tester/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/scenario-tester/Cargo.toml b/contracts/feature-tests/scenario-tester/Cargo.toml index 7b84dbf8c1..69bbb7ced4 100644 --- a/contracts/feature-tests/scenario-tester/Cargo.toml +++ b/contracts/feature-tests/scenario-tester/Cargo.toml @@ -9,10 +9,8 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental", "bls"] -path = "../../../framework/scenario" diff --git a/contracts/feature-tests/scenario-tester/meta/Cargo.toml b/contracts/feature-tests/scenario-tester/meta/Cargo.toml index 61512f3dba..8a7c2a42c9 100644 --- a/contracts/feature-tests/scenario-tester/meta/Cargo.toml +++ b/contracts/feature-tests/scenario-tester/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/std-contract/Cargo.toml b/contracts/feature-tests/std-contract/Cargo.toml index 2606f80efa..cbf21fc324 100644 --- a/contracts/feature-tests/std-contract/Cargo.toml +++ b/contracts/feature-tests/std-contract/Cargo.toml @@ -9,12 +9,7 @@ publish = false path = "src/std_contract.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" - -[dev-dependencies] -num-bigint = "0.4" +workspace = true diff --git a/contracts/feature-tests/std-contract/meta/Cargo.toml b/contracts/feature-tests/std-contract/meta/Cargo.toml index 5be8b0443c..7612e7c8df 100644 --- a/contracts/feature-tests/std-contract/meta/Cargo.toml +++ b/contracts/feature-tests/std-contract/meta/Cargo.toml @@ -8,6 +8,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/feature-tests/tiny/Cargo.toml b/contracts/feature-tests/tiny/Cargo.toml index 78b69a823d..422c5e51a4 100644 --- a/contracts/feature-tests/tiny/Cargo.toml +++ b/contracts/feature-tests/tiny/Cargo.toml @@ -8,6 +8,5 @@ publish = false path = "src/lib.rs" [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../../framework/scenario" +workspace = true features = ["wasmer-experimental"] diff --git a/contracts/feature-tests/tiny/interactor/Cargo.toml b/contracts/feature-tests/tiny/interactor/Cargo.toml index 71f2147ba1..a7f6478572 100644 --- a/contracts/feature-tests/tiny/interactor/Cargo.toml +++ b/contracts/feature-tests/tiny/interactor/Cargo.toml @@ -13,8 +13,7 @@ path = "src/tiny_interactor_main.rs" path = "src/tiny_interactor.rs" [dependencies.multiversx-sc-snippets] -version = "0.66.2" -path = "../../../../framework/snippets" +workspace = true [dependencies] clap = { version = "4.4", features = ["derive"] } diff --git a/contracts/feature-tests/tiny/interactor/config.toml b/contracts/feature-tests/tiny/interactor/config.toml index 1e629db942..feb8a005b6 100644 --- a/contracts/feature-tests/tiny/interactor/config.toml +++ b/contracts/feature-tests/tiny/interactor/config.toml @@ -1,3 +1,9 @@ +[connection] chain_type = 'real' gateway_uri = 'https://testnet-gateway.multiversx.com' +[owner] +test_wallet = 'alice' + +[wallet] +test_wallet = 'alice' diff --git a/contracts/feature-tests/tiny/interactor/src/tiny_interactor.rs b/contracts/feature-tests/tiny/interactor/src/tiny_interactor.rs index 94c060164e..ee57ce797b 100644 --- a/contracts/feature-tests/tiny/interactor/src/tiny_interactor.rs +++ b/contracts/feature-tests/tiny/interactor/src/tiny_interactor.rs @@ -1,21 +1,48 @@ mod tiny_interactor_cli; -mod tiny_interactor_config; -mod tiny_interactor_state; use clap::Parser; -pub use tiny_interactor_config::Config; -use tiny_interactor_state::State; - use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; + +/// Tiny Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub connection: ConnectionConfig, + pub owner: WalletConfig, + pub wallet: WalletConfig, +} + +impl InteractorConfig for Config { + fn connection(&self) -> &ConnectionConfig { + &self.connection + } + + fn register_wallets(&self) -> Vec { + vec![self.owner.wallet().clone(), self.wallet.wallet().clone()] + } +} + +/// Tiny Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + pub sc_address: Option, +} + +impl State { + /// Returns the contract address + pub fn current_contract_address(&self) -> &Bech32Address { + self.sc_address + .as_ref() + .expect("no known contract, deploy first") + } +} const CODE_PATH: FilePath = FilePath("../tiny.wasm"); pub async fn tiny_interactor_cli() { env_logger::init(); - let config = Config::load_config(); - - let mut basic_interact = TinyInteractor::new(config).await; + let mut basic_interact = TinyInteractor::new().await; let cli = tiny_interactor_cli::InteractCli::parse(); match &cli.command { @@ -31,27 +58,19 @@ pub async fn tiny_interactor_cli() { pub struct TinyInteractor { pub interactor: Interactor, - pub owner_address: Bech32Address, - pub wallet_address: Bech32Address, - pub state: State, + pub config: Config, + pub state: AutoSave, } impl TinyInteractor { - pub async fn new(config: Config) -> Self { - let mut interactor = Interactor::new(config.gateway_uri()) - .await - .use_chain_simulator(config.use_chain_simulator()); - - let owner_address = interactor.register_wallet(test_wallets::mike()).await; - let wallet_address = interactor.register_wallet(test_wallets::ivan()).await; - - interactor.generate_blocks(30u64).await.unwrap(); - - TinyInteractor { + pub async fn new() -> Self { + let mut interactor = Interactor::empty().with_current_dir(env!("CARGO_MANIFEST_DIR")); + let config: Config = interactor.load_config_toml().await; + let state = interactor.load_state::(); + Self { interactor, - owner_address: owner_address.into(), - wallet_address: wallet_address.into(), - state: State::load_state(), + config, + state, } } @@ -63,10 +82,11 @@ impl TinyInteractor { } pub async fn deploy(&mut self) { + let owner_address = self.config.owner.address(); let new_address = self .interactor .tx() - .from(&self.owner_address.clone()) + .from(&owner_address.clone()) .gas(600_000) .raw_deploy() .code(CODE_PATH) @@ -75,13 +95,13 @@ impl TinyInteractor { .await; println!("new address: {new_address}"); - self.state.set_sc_address(new_address); + self.state.sc_address = Some(new_address); } pub async fn call_x(&mut self) { self.interactor .tx() - .from(&self.wallet_address) + .from(self.config.wallet.address()) .to(self.state.current_contract_address()) .gas(1_100_000) .raw_call("x") diff --git a/contracts/feature-tests/tiny/interactor/src/tiny_interactor_config.rs b/contracts/feature-tests/tiny/interactor/src/tiny_interactor_config.rs deleted file mode 100644 index d9802f6436..0000000000 --- a/contracts/feature-tests/tiny/interactor/src/tiny_interactor_config.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Deserialize; -use std::io::Read; - -/// Config file -const CONFIG_FILE: &str = "config.toml"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChainType { - Real, - Simulator, -} - -#[derive(Debug, Deserialize)] -pub struct Config { - pub gateway_uri: String, - pub chain_type: ChainType, -} - -impl Config { - // Deserializes config from file - pub fn load_config() -> Self { - let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } - - pub fn chain_simulator_config() -> Self { - Config { - gateway_uri: "http://localhost:8085".to_owned(), - chain_type: ChainType::Simulator, - } - } - - // Returns the gateway URI - pub fn gateway_uri(&self) -> &str { - &self.gateway_uri - } - - // Returns if chain type is chain simulator - pub fn use_chain_simulator(&self) -> bool { - match self.chain_type { - ChainType::Real => false, - ChainType::Simulator => true, - } - } -} diff --git a/contracts/feature-tests/tiny/interactor/src/tiny_interactor_state.rs b/contracts/feature-tests/tiny/interactor/src/tiny_interactor_state.rs deleted file mode 100644 index ef2161bf7f..0000000000 --- a/contracts/feature-tests/tiny/interactor/src/tiny_interactor_state.rs +++ /dev/null @@ -1,48 +0,0 @@ -use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -/// State file -const STATE_FILE: &str = "state.toml"; - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - sc_address: Option, -} - -impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - pub fn set_sc_address(&mut self, address: Bech32Address) { - self.sc_address = Some(address); - } - - /// Returns the contract address - pub fn current_contract_address(&self) -> &Bech32Address { - self.sc_address - .as_ref() - .expect("no known contract, deploy first") - } -} - -impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/contracts/feature-tests/use-module/Cargo.toml b/contracts/feature-tests/use-module/Cargo.toml index 9b7ae5999f..f49bdbf5a4 100644 --- a/contracts/feature-tests/use-module/Cargo.toml +++ b/contracts/feature-tests/use-module/Cargo.toml @@ -9,18 +9,14 @@ publish = false path = "src/use_module.rs" [dependencies.multiversx-sc-modules] -version = "0.66.2" -path = "../../../contracts/modules" +workspace = true [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../../framework/base" +workspace = true [dev-dependencies.multiversx-sc-scenario] -version = "0.66.2" +workspace = true features = ["wasmer-experimental"] -path = "../../../framework/scenario" [dev-dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../framework/meta-lib" +workspace = true diff --git a/contracts/feature-tests/use-module/meta/Cargo.toml b/contracts/feature-tests/use-module/meta/Cargo.toml index 0ebb64cbb9..0d5189b3ed 100644 --- a/contracts/feature-tests/use-module/meta/Cargo.toml +++ b/contracts/feature-tests/use-module/meta/Cargo.toml @@ -9,6 +9,4 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../../../framework/meta-lib" -default-features = false +workspace = true diff --git a/contracts/modules/Cargo.toml b/contracts/modules/Cargo.toml index bef70ee1dd..a89933a0f8 100644 --- a/contracts/modules/Cargo.toml +++ b/contracts/modules/Cargo.toml @@ -17,5 +17,4 @@ categories = ["no-std", "wasm", "cryptography::cryptocurrencies"] alloc = ["multiversx-sc/alloc"] [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../framework/base" +workspace = true diff --git a/data/codec/Cargo.toml b/data/codec/Cargo.toml index eb8bf0fd3b..1d254e337e 100644 --- a/data/codec/Cargo.toml +++ b/data/codec/Cargo.toml @@ -34,7 +34,7 @@ arrayvec = { version = "0.7", default-features = false } num-bigint = { version = "0.4", optional = true } # can only be used in std contexts num-traits = { version = "0.2", default-features = false } unwrap-infallible = "1.0" -bitflags = "2.9" +bitflags = "2.13" [dev-dependencies.multiversx-sc-codec-derive] path = "../codec-derive" diff --git a/data/codec/src/single/nested_de.rs b/data/codec/src/single/nested_de.rs index ec62a31b86..e0870c338a 100644 --- a/data/codec/src/single/nested_de.rs +++ b/data/codec/src/single/nested_de.rs @@ -17,13 +17,7 @@ pub trait NestedDecode: Sized { fn dep_decode_or_handle_err(input: &mut I, h: H) -> Result where I: NestedDecodeInput, - H: DecodeErrorHandler, - { - match Self::dep_decode(input) { - Ok(v) => Ok(v), - Err(e) => Err(h.handle_error(e)), - } - } + H: DecodeErrorHandler; /// Allows the framework to do monomorphisation of special cases where the data is of type `u8`. /// diff --git a/data/codec/src/single/nested_en.rs b/data/codec/src/single/nested_en.rs index cb193b8975..b9cc9c72b5 100644 --- a/data/codec/src/single/nested_en.rs +++ b/data/codec/src/single/nested_en.rs @@ -18,13 +18,7 @@ pub trait NestedEncode: Sized { fn dep_encode_or_handle_err(&self, dest: &mut O, h: H) -> Result<(), H::HandledErr> where O: NestedEncodeOutput, - H: EncodeErrorHandler, - { - match self.dep_encode(dest) { - Ok(()) => Ok(()), - Err(e) => Err(h.handle_error(e)), - } - } + H: EncodeErrorHandler; /// Allows the framework to do monomorphisation of special cases where the data is of type `u8`. /// diff --git a/data/codec/src/single/top_de.rs b/data/codec/src/single/top_de.rs index 921d9c50bb..01a1447f96 100644 --- a/data/codec/src/single/top_de.rs +++ b/data/codec/src/single/top_de.rs @@ -33,13 +33,7 @@ pub trait TopDecode: Sized { fn top_decode_or_handle_err(input: I, h: H) -> Result where I: TopDecodeInput, - H: DecodeErrorHandler, - { - match Self::top_decode(input) { - Ok(v) => Ok(v), - Err(e) => Err(h.handle_error(e)), - } - } + H: DecodeErrorHandler; /// Allows types to provide optimized implementations for their boxed version. /// Especially designed for byte arrays that can be transmuted directly from the input sometimes. diff --git a/data/codec/src/single/top_en.rs b/data/codec/src/single/top_en.rs index 7871cf55f9..e79a5b690b 100644 --- a/data/codec/src/single/top_en.rs +++ b/data/codec/src/single/top_en.rs @@ -20,13 +20,7 @@ pub trait TopEncode: Sized { fn top_encode_or_handle_err(&self, output: O, h: H) -> Result<(), H::HandledErr> where O: TopEncodeOutput, - H: EncodeErrorHandler, - { - match self.top_encode(output) { - Ok(()) => Ok(()), - Err(e) => Err(h.handle_error(e)), - } - } + H: EncodeErrorHandler; } pub fn top_encode_from_nested(obj: &T, output: O, h: H) -> Result<(), H::HandledErr> diff --git a/data/human-readable/Cargo.toml b/data/human-readable/Cargo.toml index c35153ad28..435a95367d 100644 --- a/data/human-readable/Cargo.toml +++ b/data/human-readable/Cargo.toml @@ -18,5 +18,4 @@ categories = ["cryptography::cryptocurrencies", "development-tools"] serde_json = { version = "1.0" } [dependencies.multiversx-sc-scenario] -version = "=0.66.2" -path = "../../framework/scenario" +workspace = true diff --git a/framework/base/Cargo.toml b/framework/base/Cargo.toml index c946c9785b..6f5fe22a1c 100644 --- a/framework/base/Cargo.toml +++ b/framework/base/Cargo.toml @@ -27,7 +27,7 @@ contract-call-legacy = [] small-int-bug = [] # can be used for testing the buggy mBufferFromSmallIntSigned VM hook [dependencies] -bitflags = "2.9" +bitflags = "2.13" num-traits = { version = "0.2", default-features = false } unwrap-infallible = "1.0" generic-array = "1.2" diff --git a/framework/base/src/contract_base/wrappers/blockchain_wrapper.rs b/framework/base/src/contract_base/wrappers/blockchain_wrapper.rs index 0d16849fbb..ba27d63504 100644 --- a/framework/base/src/contract_base/wrappers/blockchain_wrapper.rs +++ b/framework/base/src/contract_base/wrappers/blockchain_wrapper.rs @@ -165,7 +165,7 @@ where ManagedRefMut::<'static, A, ManagedBuffer>::wrap_handle(mbuf_temp_1) .load_to_byte_array(&mut buffer); } - CodeMetadata::from(buffer) + CodeMetadata::from_bytes_or_default(&buffer) } pub fn get_code_hash(&self, address: &ManagedAddress) -> ManagedBuffer { diff --git a/framework/base/src/formatter/formatter_impl_vm_core.rs b/framework/base/src/formatter/formatter_impl_vm_core.rs index 26d12294c2..1653a04b54 100644 --- a/framework/base/src/formatter/formatter_impl_vm_core.rs +++ b/framework/base/src/formatter/formatter_impl_vm_core.rs @@ -4,7 +4,7 @@ use super::{FormatByteReceiver, SCBinary, SCDisplay, SCLowerHex, hex_util}; impl SCDisplay for CodeMetadata { fn fmt(&self, f: &mut F) { - self.for_each_string_token(|token| f.append_bytes(token.as_bytes())) + self.write_display_tokens(|token| f.append_bytes(token.as_bytes())) } } diff --git a/framework/base/src/imports.rs b/framework/base/src/imports.rs index dcddd2afa5..1bd6ed160a 100644 --- a/framework/base/src/imports.rs +++ b/framework/base/src/imports.rs @@ -20,7 +20,7 @@ pub use crate::{ }; #[cfg(feature = "std")] -pub use multiversx_chain_core::std::Bech32Address; +pub use multiversx_chain_core::std::{Bech32Address, Bech32Hrp}; pub use core::ops::{ Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, diff --git a/framework/base/src/storage/mappers/map_storage_mapper.rs b/framework/base/src/storage/mappers/map_storage_mapper.rs index 5635ace7f6..ce93efcae2 100644 --- a/framework/base/src/storage/mappers/map_storage_mapper.rs +++ b/framework/base/src/storage/mappers/map_storage_mapper.rs @@ -166,8 +166,22 @@ where /// If the map did not have this key present, `true` is returned. /// /// If the map did have this value present, `false` is returned. + /// + /// Any residual storage at the nested namespace for this key is cleared before + /// the key is inserted, ensuring the new entry always starts from a clean state. pub fn insert_default(&mut self, k: K) -> bool { - self.keys_set.insert(k) + // Build the mapper handle before consuming `k` so the key is encoded once. + // `SetMapper::insert` already performs the `contains` check internally, + // avoiding a redundant storage read compared to calling `contains` first. + let mut mapped = self.get_mapped_storage_value(&k); + if self.keys_set.insert(k) { + // Clear any residual nested storage to prevent stale state from a previous + // occupant of the same key from becoming visible. + mapped.clear(); + true + } else { + false + } } /// Removes the entry from the map. @@ -530,6 +544,19 @@ where } /// Gets the value in the entry. + /// + /// Returns an owned mapper handle that points to the deterministic nested storage + /// namespace for this key. + /// + /// # Warning + /// + /// The returned mapper is a lightweight handle to a derived storage key prefix. + /// It is not invalidated when the entry is subsequently removed via [`OccupiedEntry::remove`]. + /// Writing through a mapper obtained before removal will recreate storage at the same + /// namespace even though the key is no longer tracked in `keys_set`, producing hidden + /// state that is invisible to normal map lookups until the key is reinserted. + /// Do **not** retain and write through a mapper handle after calling `remove()` on + /// the entry it was obtained from. pub fn get(&self) -> V { self.map.get(&self.key).unwrap() } @@ -549,7 +576,14 @@ where f(&mut value) } - /// Removes the entry from the map. + /// Removes the entry from the map and clears all nested storage under the entry's key. + /// + /// # Warning + /// + /// Any mapper handle previously obtained via [`OccupiedEntry::get`] for this entry + /// still points to the same storage prefix. Writing through such a handle after + /// calling `remove()` recreates storage at that namespace outside `keys_set` tracking. + /// Ensure all mapper handles obtained from this entry are dropped before calling `remove()`. pub fn remove(self) { self.map.remove(&self.key); } diff --git a/framework/base/src/types/interaction/tx.rs b/framework/base/src/types/interaction/tx.rs index 490b99ab8c..9e280b88ae 100644 --- a/framework/base/src/types/interaction/tx.rs +++ b/framework/base/src/types/interaction/tx.rs @@ -12,9 +12,9 @@ use super::{ AnnotatedValue, Code, DeployCall, Egld, EgldPayment, ExplicitGas, FromSource, FunctionCall, ManagedArgBuffer, OriginalResultMarker, RHList, RHListAppendNoRet, RHListAppendRet, RHListItem, TxCodeSource, TxCodeValue, TxData, TxDataFunctionCall, TxEgldValue, TxEnv, - TxEnvMockDeployAddress, TxEnvWithTxHash, TxFrom, TxFromSourceValue, TxFromSpecified, TxGas, - TxGasValue, TxPayment, TxPaymentEgldOnly, TxProxyTrait, TxResultHandler, TxTo, TxToSpecified, - UpgradeCall, + TxEnvMockDeployAddress, TxEnvWithRelayer, TxEnvWithTxHash, TxFrom, TxFromSourceValue, + TxFromSpecified, TxGas, TxGasValue, TxPayment, TxPaymentEgldOnly, TxProxyTrait, + TxResultHandler, TxTo, TxToSpecified, UpgradeCall, }; /// Universal representation of a blockchain transaction. @@ -993,3 +993,42 @@ where self } } + +impl Tx +where + Env: TxEnvWithRelayer, + From: TxFrom, + To: TxTo, + Payment: TxPayment, + Gas: TxGas, + Data: TxData, + RH: TxResultHandler, +{ + /// Sets the relayer address for a relayed v3 transaction. + /// + /// The relayer pays the gas fees. After the sender signs the transaction, + /// the relayer must also sign it (adding `relayerSignature`) before broadcasting. + pub fn relayer(mut self, relayer: A) -> Self + where + A: AnnotatedValue>, + { + let relayer_addr = relayer.into_value(&self.env); + self.env.set_relayer_address(relayer_addr); + self + } + + /// Optionally sets the relayer address for a relayed v3 transaction. + /// + /// The relayer pays the gas fees. After the sender signs the transaction, + /// the relayer must also sign it (adding `relayerSignature`) before broadcasting. + pub fn opt_relayer(self, relayer: Option) -> Self + where + A: AnnotatedValue>, + { + if let Some(relayer) = relayer { + self.relayer(relayer) + } else { + self + } + } +} diff --git a/framework/base/src/types/interaction/tx_data/upgrade_call.rs b/framework/base/src/types/interaction/tx_data/upgrade_call.rs index 591e8accb5..185ef43396 100644 --- a/framework/base/src/types/interaction/tx_data/upgrade_call.rs +++ b/framework/base/src/types/interaction/tx_data/upgrade_call.rs @@ -23,7 +23,7 @@ where fn default() -> UpgradeCall { UpgradeCall { code_source: (), - code_metadata: CodeMetadata::DEFAULT, + code_metadata: CodeMetadata::EMPTY, arg_buffer: ManagedArgBuffer::new(), } } diff --git a/framework/base/src/types/interaction/tx_env.rs b/framework/base/src/types/interaction/tx_env.rs index d1e06c9f69..d3a56a1952 100644 --- a/framework/base/src/types/interaction/tx_env.rs +++ b/framework/base/src/types/interaction/tx_env.rs @@ -38,3 +38,10 @@ pub trait TxEnvWithTxHash: TxEnv { /// Retrieves current tx hash, while resetting it in self. fn take_tx_hash(&mut self) -> Option; } + +pub trait TxEnvWithRelayer: TxEnv { + fn set_relayer_address(&mut self, relayer: ManagedAddress); + + /// Retrieves the relayer address, while resetting it in self. + fn take_relayer_address(&mut self) -> Option>; +} diff --git a/framework/base/tests/formatter_test.rs b/framework/base/tests/formatter_test.rs index 1519374672..04038b3c95 100644 --- a/framework/base/tests/formatter_test.rs +++ b/framework/base/tests/formatter_test.rs @@ -140,7 +140,7 @@ fn test_display_code_metadata() { | CodeMetadata::PAYABLE_BY_SC, "Upgradeable|Readable|Payable|PayableBySC", ); - check_code_metadata_display(CodeMetadata::DEFAULT, "Default"); + check_code_metadata_display(CodeMetadata::EMPTY, "Default"); } fn check_lower_hex_eq(item: T, expected: &str) { diff --git a/framework/meta-lib/Cargo.toml b/framework/meta-lib/Cargo.toml index 650207888a..4c8d773701 100644 --- a/framework/meta-lib/Cargo.toml +++ b/framework/meta-lib/Cargo.toml @@ -26,8 +26,8 @@ colored = "3.0" lazy_static = "1.4.0" convert_case = "0.11" hex = "0.4" -wasmparser = "0.249" -wasmprinter = "0.249" +wasmparser = "0.252" +wasmprinter = "0.252" semver = "1.0.20" wat = "1.217.0" diff --git a/framework/meta-lib/src/cargo_toml.rs b/framework/meta-lib/src/cargo_toml.rs index 4bdb7da87d..bb9826bd30 100644 --- a/framework/meta-lib/src/cargo_toml.rs +++ b/framework/meta-lib/src/cargo_toml.rs @@ -2,6 +2,7 @@ mod cargo_toml_contents; mod cargo_toml_deps; mod cargo_toml_deps_raw; mod version_req; +mod workspace_dependencies; pub use cargo_toml_contents::{ CARGO_TOML_DEPENDENCIES, CARGO_TOML_DEV_DEPENDENCIES, CargoTomlContents, @@ -10,3 +11,4 @@ pub use cargo_toml_contents::{ pub use cargo_toml_deps::{DependencyReference, GitCommitReference}; pub use cargo_toml_deps_raw::DependencyRawValue; pub use version_req::VersionReq; +pub use workspace_dependencies::WorkspaceDependencies; diff --git a/framework/meta-lib/src/cargo_toml/cargo_toml_contents.rs b/framework/meta-lib/src/cargo_toml/cargo_toml_contents.rs index 776b8f4847..1208d4e988 100644 --- a/framework/meta-lib/src/cargo_toml/cargo_toml_contents.rs +++ b/framework/meta-lib/src/cargo_toml/cargo_toml_contents.rs @@ -8,7 +8,7 @@ use toml::{Value, value::Table}; use crate::contract::sc_config::ContractVariantProfile; -use super::DependencyRawValue; +use super::{DependencyRawValue, WorkspaceDependencies}; pub const CARGO_TOML_DEPENDENCIES: &str = "dependencies"; pub const CARGO_TOML_DEV_DEPENDENCIES: &str = "dev-dependencies"; @@ -154,6 +154,65 @@ impl CargoTomlContents { .map(DependencyRawValue::parse_toml_value) } + /// Returns the directory containing this manifest. + pub fn manifest_dir(&self) -> &Path { + self.path.parent().unwrap_or_else(|| Path::new(".")) + } + + /// Resolves inherited `workspace = true` dependencies in both `[dependencies]` and + /// `[dev-dependencies]` using dependencies declared in the workspace root. + /// Returns `true` if any entry was replaced. + pub fn resolve_workspace_dependencies( + &mut self, + workspace_dependencies: &WorkspaceDependencies, + ) -> bool { + if workspace_dependencies.is_empty() { + return false; + } + + let manifest_dir = self.manifest_dir().to_path_buf(); + let mut changed = false; + + if self.has_dependencies() { + changed |= resolve_workspace_dependencies_in_table( + self.dependencies_mut(), + workspace_dependencies, + &manifest_dir, + ); + } + if self.has_dev_dependencies() { + changed |= resolve_workspace_dependencies_in_table( + self.dev_dependencies_mut(), + workspace_dependencies, + &manifest_dir, + ); + } + + changed + } +} + +fn resolve_workspace_dependencies_in_table( + deps_map: &mut Table, + workspace_dependencies: &WorkspaceDependencies, + manifest_dir: &Path, +) -> bool { + let mut changed = false; + for (crate_name, value) in deps_map { + let mut local_dep = DependencyRawValue::parse_toml_value(value); + if !local_dep.workspace { + continue; + } + + let workspace_dep = workspace_dependencies.resolve_dependency(crate_name, manifest_dir); + local_dep.replace_workspace_dep(&workspace_dep); + *value = local_dep.into_toml_value(); + changed = true; + } + changed +} + +impl CargoTomlContents { pub fn insert_dependency_raw_value(&mut self, crate_name: &str, raw_value: DependencyRawValue) { self.dependencies_mut() .insert(crate_name.to_owned(), raw_value.into_toml_value()); @@ -425,7 +484,9 @@ fn remove_quotes(var: &Value) -> String { #[cfg(test)] mod tests { use super::*; - use crate::cargo_toml::{DependencyReference, GitCommitReference, VersionReq}; + use crate::cargo_toml::{ + DependencyReference, GitCommitReference, VersionReq, WorkspaceDependencies, + }; #[test] fn test_change_from_base_to_adapter_path() { @@ -458,9 +519,17 @@ version = "=0.54.1" git = "https://github.com/multiversx/repo1" rev = "85c31b9ce730bd5ffe41589c353d935a14baaa96" +[dependencies.by-workspace] +workspace = true +features = ["alloc", "std"] + [dependencies.by-path-1] path = "a/b/c" +[workspace.dependencies.by-workspace-root] +version = "0.54.4" +path = "g/h/i" + [dependencies] by-version-2 = "0.54.2" by-version-2-strict = "=0.54.3" @@ -568,6 +637,18 @@ by-git-commit-2 = { git = "https://github.com/multiversx/repo2", rev = "e990be82 }) ); + // workspace + let raw_value = cargo_toml.dependency_raw_value("by-workspace").unwrap(); + assert_eq!( + raw_value, + DependencyRawValue { + workspace: true, + features: ["alloc".to_owned(), "std".to_owned()].into(), + ..Default::default() + }, + ); + assert_eq!(raw_value.interpret(), DependencyReference::Workspace,); + // path let raw_value = cargo_toml.dependency_raw_value("by-path-1").unwrap(); let path = Path::new("a").join("b").join("c"); @@ -592,4 +673,67 @@ by-git-commit-2 = { git = "https://github.com/multiversx/repo2", rev = "e990be82 ); assert_eq!(raw_value.interpret(), DependencyReference::Path(path),); } + + #[test] + fn test_resolve_workspace_dependencies() { + let mut cargo_toml = CargoTomlContents::parse_string( + r#" +[dependencies.by-workspace] +workspace = true +features = ["std"] + +[dev-dependencies.by-dev-workspace] +workspace = true + +[workspace.dependencies.by-workspace] +version = "0.54.0" +path = "framework/base" +features = ["alloc"] + +[workspace.dependencies.by-dev-workspace] +version = "0.54.1" +path = "framework/scenario" +"#, + Path::new("/repo/contracts/examples/adder/Cargo.toml"), + ); + let workspace_dependencies = + WorkspaceDependencies::from_cargo_toml(Path::new("/repo"), &cargo_toml.clone()); + + cargo_toml.resolve_workspace_dependencies(&workspace_dependencies); + + let path = Path::new("..") + .join("..") + .join("..") + .join("framework") + .join("base"); + assert_eq!( + cargo_toml.dependency_raw_value("by-workspace").unwrap(), + DependencyRawValue { + version: Some("0.54.0".to_owned()), + path: Some(path), + features: ["std".to_owned()].into(), + ..Default::default() + }, + ); + + let dev_deps = cargo_toml + .toml_value + .get(CARGO_TOML_DEV_DEPENDENCIES) + .unwrap() + .as_table() + .unwrap(); + let path = Path::new("..") + .join("..") + .join("..") + .join("framework") + .join("scenario"); + assert_eq!( + DependencyRawValue::parse_toml_value(dev_deps.get("by-dev-workspace").unwrap()), + DependencyRawValue { + version: Some("0.54.1".to_owned()), + path: Some(path), + ..Default::default() + }, + ); + } } diff --git a/framework/meta-lib/src/cargo_toml/cargo_toml_deps.rs b/framework/meta-lib/src/cargo_toml/cargo_toml_deps.rs index 632b545e85..15f6476759 100644 --- a/framework/meta-lib/src/cargo_toml/cargo_toml_deps.rs +++ b/framework/meta-lib/src/cargo_toml/cargo_toml_deps.rs @@ -29,6 +29,7 @@ pub struct GitTagReference { #[derive(Debug, Clone, PartialEq, Eq)] pub enum DependencyReference { Version(VersionReq), + Workspace, GitCommit(GitCommitReference), GitBranch(GitBranchReference), GitTag(GitTagReference), @@ -57,6 +58,10 @@ impl DependencyReference { impl DependencyRawValue { /// Interprets the raw dependency value as one of several possible formats. pub fn interpret(self) -> DependencyReference { + if self.workspace { + return DependencyReference::Workspace; + } + // path is top priority if let Some(path) = self.path { return DependencyReference::Path(path); diff --git a/framework/meta-lib/src/cargo_toml/cargo_toml_deps_raw.rs b/framework/meta-lib/src/cargo_toml/cargo_toml_deps_raw.rs index 40b02bf5b3..96527ee94b 100644 --- a/framework/meta-lib/src/cargo_toml/cargo_toml_deps_raw.rs +++ b/framework/meta-lib/src/cargo_toml/cargo_toml_deps_raw.rs @@ -1,14 +1,38 @@ use std::{collections::BTreeSet, path::PathBuf}; -#[derive(Default, Debug, Clone, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +fn is_false(b: &bool) -> bool { + !*b +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DependencyRawValue { + #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, + #[serde(default, skip_serializing_if = "is_false")] + pub workspace: bool, + #[serde(skip_serializing_if = "Option::is_none")] pub git: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub rev: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub branch: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tag: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, + #[serde(default, skip_serializing_if = "BTreeSet::is_empty")] pub features: BTreeSet, + #[serde(rename = "default-features", skip_serializing_if = "Option::is_none")] + pub default_features: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub optional: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub package: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub registry: Option, } impl DependencyRawValue { @@ -22,74 +46,32 @@ impl DependencyRawValue { pub fn parse_toml_value(toml_value: &toml::Value) -> Self { match toml_value { toml::Value::String(version) => DependencyRawValue::from_version(version), - toml::Value::Table(table) => { - let mut result = DependencyRawValue::default(); - if let Some(toml::Value::String(version)) = table.get("version") { - result.version = Some(version.to_owned()); - } - if let Some(toml::Value::String(path)) = table.get("path") { - result.path = Some(PathBuf::from(path)); - } - if let Some(toml::Value::String(git)) = table.get("git") { - result.git = Some(git.to_owned()); - } - if let Some(toml::Value::String(rev)) = table.get("rev") { - result.rev = Some(rev.to_owned()); - } - if let Some(toml::Value::String(branch)) = table.get("branch") { - result.branch = Some(branch.to_owned()); - } - if let Some(toml::Value::String(tag)) = table.get("tag") { - result.tag = Some(tag.to_owned()); - } - result - } - _ => panic!("Unsupported dependency value"), + _ => toml_value + .clone() + .try_into() + .unwrap_or_else(|e| panic!("failed to parse dependency value: {e}")), } } pub fn into_toml_value(self) -> toml::Value { - let mut table = toml::map::Map::new(); - - if let Some(version) = self.version { - table.insert("version".to_string(), toml::Value::String(version)); - } - - if let Some(git) = self.git { - table.insert("git".to_string(), toml::Value::String(git)); - } - - if let Some(rev) = self.rev { - table.insert("rev".to_string(), toml::Value::String(rev)); - } - - if let Some(branch) = self.branch { - table.insert("branch".to_string(), toml::Value::String(branch)); - } - - if let Some(tag) = self.tag { - table.insert("tag".to_string(), toml::Value::String(tag)); - } - - if let Some(path) = self.path { - table.insert( - "path".to_string(), - toml::Value::String(path.to_string_lossy().into_owned()), - ); - } - - if !self.features.is_empty() { - table.insert( - "features".to_string(), - toml::Value::Array( - self.features - .iter() - .map(|feature| toml::Value::String(feature.clone())) - .collect(), - ), - ); - } + toml::Value::try_from(self).expect("failed to serialize dependency value") + } - toml::Value::Table(table) + /// Removes the `workspace = true` flag and replaces the dependency fields from `workspace_dep`. + /// + /// Copies specification fields (version, git, rev, branch, tag, path, default-features, + /// package, registry) from the workspace dep. Does not touch `features` or `optional`, + /// which are local-only attributes that the crate controls independently. + pub fn replace_workspace_dep(&mut self, workspace_dep: &DependencyRawValue) { + self.workspace = false; + self.version = workspace_dep.version.clone(); + self.git = workspace_dep.git.clone(); + self.rev = workspace_dep.rev.clone(); + self.branch = workspace_dep.branch.clone(); + self.tag = workspace_dep.tag.clone(); + self.path = workspace_dep.path.clone(); + self.default_features = workspace_dep.default_features; + self.package = workspace_dep.package.clone(); + self.registry = workspace_dep.registry.clone(); } } diff --git a/framework/meta-lib/src/cargo_toml/workspace_dependencies.rs b/framework/meta-lib/src/cargo_toml/workspace_dependencies.rs new file mode 100644 index 0000000000..b047ab6f7b --- /dev/null +++ b/framework/meta-lib/src/cargo_toml/workspace_dependencies.rs @@ -0,0 +1,172 @@ +use std::{ + collections::BTreeMap, + path::{Component, Path, PathBuf}, +}; + +use toml::value::Table; + +use crate::tools::find_workspace; + +use super::{CARGO_TOML_DEPENDENCIES, CargoTomlContents, DependencyRawValue}; + +const WORKSPACE: &str = "workspace"; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct WorkspaceDependencies { + pub workspace_path: PathBuf, + pub dependencies: BTreeMap, +} + +impl WorkspaceDependencies { + pub fn load_from_dir(workspace_path: impl AsRef) -> Self { + let workspace_path = workspace_path.as_ref(); + let cargo_toml_contents = + CargoTomlContents::load_from_file(workspace_path.join("Cargo.toml")); + + Self::from_cargo_toml(workspace_path, &cargo_toml_contents) + } + + /// Like [`load_from_dir`](Self::load_from_dir), but returns an empty + /// `WorkspaceDependencies` if no `Cargo.toml` exists at `workspace_path` + /// instead of panicking. + pub fn try_load_from_dir(workspace_path: impl AsRef) -> Option { + let workspace_path = workspace_path.as_ref(); + let cargo_toml_path = workspace_path.join("Cargo.toml"); + if !cargo_toml_path.is_file() { + return None; + } + Some(Self::load_from_dir(workspace_path)) + } + + pub fn find_from_dir(dir: impl AsRef) -> Option { + let workspace_path = find_workspace(dir.as_ref())?; + Self::try_load_from_dir(workspace_path) + } + + pub fn from_cargo_toml( + workspace_path: impl AsRef, + cargo_toml_contents: &CargoTomlContents, + ) -> Self { + let dependencies = Self::dependencies_table(cargo_toml_contents) + .map(Self::parse_dependencies_table) + .unwrap_or_default(); + + Self { + workspace_path: workspace_path.as_ref().to_path_buf(), + dependencies, + } + } + + pub fn is_empty(&self) -> bool { + self.dependencies.is_empty() + } + + pub fn get(&self, crate_name: &str) -> Option<&DependencyRawValue> { + self.dependencies.get(crate_name) + } + + /// Resolves a single dependency from workspace root to a concrete value. + /// + /// This rebases workspace-relative `path` entries to be relative to `relative_dir`. + pub(crate) fn resolve_dependency( + &self, + crate_name: &str, + relative_dir: &Path, + ) -> DependencyRawValue { + let mut workspace_dependency = self + .get(crate_name) + .unwrap_or_else(|| panic!("missing workspace dependency in Cargo.toml: {crate_name}")) + .clone(); + + if let Some(path) = &mut workspace_dependency.path { + *path = path_relative_from_to( + &absolute_path(&self.workspace_path).join(&path), + &absolute_path(relative_dir), + ); + } + + workspace_dependency + } + + fn dependencies_table(cargo_toml_contents: &CargoTomlContents) -> Option<&Table> { + cargo_toml_contents + .toml_value + .get(WORKSPACE) + .and_then(|workspace| workspace.get(CARGO_TOML_DEPENDENCIES)) + .and_then(|deps| deps.as_table()) + } + + fn parse_dependencies_table(deps: &Table) -> BTreeMap { + deps.iter() + .map(|(crate_name, value)| { + ( + crate_name.to_owned(), + DependencyRawValue::parse_toml_value(value), + ) + }) + .collect() + } +} + +fn absolute_path(path: &Path) -> PathBuf { + if path.is_absolute() { + normalize_path(path) + } else { + normalize_path(&std::env::current_dir().unwrap().join(path)) + } +} + +fn normalize_path(path: &Path) -> PathBuf { + let mut result = PathBuf::new(); + for component in path.components() { + match component { + Component::CurDir => {} + Component::ParentDir => { + result.pop(); + } + _ => result.push(component.as_os_str()), + } + } + result +} + +fn path_relative_from_to(path: &Path, base: &Path) -> PathBuf { + let path = normalize_path(path); + let base = normalize_path(base); + let path_components: Vec<_> = path.components().collect(); + let base_components: Vec<_> = base.components().collect(); + let common_len = path_components + .iter() + .zip(base_components.iter()) + .take_while(|(path_component, base_component)| path_component == base_component) + .count(); + + let mut relative_path = PathBuf::new(); + for _ in common_len..base_components.len() { + relative_path.push(".."); + } + for component in &path_components[common_len..] { + relative_path.push(component.as_os_str()); + } + relative_path +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + #[test] + fn test_path_relative_from_to() { + assert_eq!( + super::path_relative_from_to( + Path::new("/repo/framework/base"), + Path::new("/repo/contracts/examples/adder"), + ), + Path::new("..") + .join("..") + .join("..") + .join("framework") + .join("base"), + ); + } +} diff --git a/framework/meta-lib/src/contract/meta_config.rs b/framework/meta-lib/src/contract/meta_config.rs index e022714712..737463b1bc 100644 --- a/framework/meta-lib/src/contract/meta_config.rs +++ b/framework/meta-lib/src/contract/meta_config.rs @@ -6,7 +6,7 @@ use std::{ use multiversx_sc::abi::ContractAbi; use crate::{ - cargo_toml::CargoTomlContents, + cargo_toml::{CargoTomlContents, WorkspaceDependencies}, cli::BuildArgs, print_util::{print_removing_wasm_crate, print_workspace_target_dir}, tools::{check_tools_installed, find_current_workspace}, @@ -68,14 +68,19 @@ impl MetaConfig { /// Cargo.toml files for all wasm crates are generated from the main contract Cargo.toml, /// by changing the package name. pub fn generate_cargo_toml_for_all_wasm_crates(&mut self) { - let main_cargo_toml_contents = + let mut main_cargo_toml_contents = CargoTomlContents::load_from_file(Path::new("..").join("Cargo.toml")); let crate_name = main_cargo_toml_contents.package_name(); + if let Some(workspace_path) = find_current_workspace() { + let workspace_dependencies = WorkspaceDependencies::load_from_dir(workspace_path); + main_cargo_toml_contents.resolve_workspace_dependencies(&workspace_dependencies); + } for contract in self.sc_config.contracts.iter() { let mut framework_dependency = main_cargo_toml_contents .dependency_raw_value(FRAMEWORK_NAME_BASE) .expect("missing framework dependency in Cargo.toml"); + framework_dependency.features.clear(); if contract.settings.std { framework_dependency.features.insert("std".to_owned()); } diff --git a/framework/meta/.gitignore b/framework/meta/.gitignore index 9b12a82495..70b43db1f8 100644 --- a/framework/meta/.gitignore +++ b/framework/meta/.gitignore @@ -1,2 +1,3 @@ tests/cs_tx_cli_test/outfiles -tests/cs_tx_cli_test/alice.pem +tests/cs_tx_cli_test/*.pem +tests/temp/ diff --git a/framework/meta/Cargo.toml b/framework/meta/Cargo.toml index 79b89d7a4a..70ad61ff46 100644 --- a/framework/meta/Cargo.toml +++ b/framework/meta/Cargo.toml @@ -26,6 +26,7 @@ template-test-released = [] chain-simulator-tests = [] repro-build-test = [] install-debugger-test = [] +ledger = ["multiversx-sdk/ledger"] [dependencies] clap = { version = "4.4", features = ["derive"] } @@ -39,16 +40,19 @@ semver = "1.0.20" ruplacer = { version = "0.10", default-features = false } regex = "1.11" reqwest = { version = "0.13", features = ["rustls", "blocking", "json"] } -copy_dir = "0.1.2" -pathdiff = "0.2.1" -common-path = "1.0.0" +copy_dir = "0.1" +pathdiff = "0.2" +common-path = "1.0" bip39 = { version = "2.0", features = ["rand"] } +rand = { version = "0.10", default-features = false } +uuid = { version = "1.10", features = ["v4"] } anyhow = "1.0" hex = "0.4.3" -base64 = "0.22" indexmap = { version = "2", features = ["serde"] } # warning: newer versions require Rust 1.88, hence we are not upgrading yet zip = { version = "7.2", features = ["deflate"], default-features = false } +# rpassword >=7.4 requires Rust 1.88 (let_chains), pin to 7.3.x for Rust 1.85 compat +rpassword = "~7.3" [dependencies.multiversx-sc-meta-lib] version = "=0.66.2" @@ -75,7 +79,6 @@ features = ["std"] [dependencies.multiversx-sdk] version = "=0.16.1" path = "../../sdk/core" -features = ["wallet-full"] [dev-dependencies] serial_test = "3.2" diff --git a/framework/meta/ledger.md b/framework/meta/ledger.md new file mode 100644 index 0000000000..1319bb3914 --- /dev/null +++ b/framework/meta/ledger.md @@ -0,0 +1,420 @@ +# Ledger Hardware Wallet Integration Plan + +## Overview + +This document outlines the plan for implementing Ledger hardware wallet support, translated from the Python SDK (`mx-sdk-py` + `mx-sdk-py-cli`). + +The goal is: +1. A `LedgerApp` struct in `sdk/core` (feature-gated) that handles raw APDU communication. +2. A `Signer` enum embedded in `Wallet`, replacing the `private_key` field, that holds either a `PrivateKey` or a Ledger signer. +3. A `--ledger` flag on `SenderArgs` (and a `ledger` field in `WalletConfig`) so any signing path can use the Ledger device. +4. A `sc-meta ledger` CLI subcommand to list addresses and query app version. + +Guardians and relayers are out of scope. + +--- + +## Python Reference + +### `LedgerApp` (`multiversx_sdk/ledger/ledger_app.py`) + +The core class. Wraps raw HID communication via the `ledgercomm` Python library. + +**APDU constants** (`CLA = 0xED`): + +| Instruction | INS | Description | +|--------------------------|------|-------------------------------------| +| `GET_APP_CONFIG` | 0x02 | App version, data activated, indexes| +| `GET_ADDRESS` | 0x03 | Get bech32 address for an index | +| `SET_ADDRESS` | 0x05 | Set active address index on device | +| `SIGN_MESSAGE` | 0x06 | Sign arbitrary message | +| `SIGN_HASH_TX` | 0x07 | Sign transaction (hash-based) | +| `PROVIDE_ESDT_INFO` | 0x08 | (Out of scope) | +| `GET_ADDRESS_AUTH_TOKEN` | 0x09 | (Out of scope) | + +**Chunked signing protocol:** +- Maximum chunk size: 150 bytes +- First chunk: `p1 = 0x00` +- Subsequent chunks: `p1 = 0x80` +- `p2 = 0x00` always +- Response: 65 bytes — byte 0 is `0x40` (length prefix), bytes 1..=64 are the ed25519 signature + +**Address derivation path:** `m/44'/508'/account'/0'/address_index'` +- `account` is always `0` for MultiversX +- `address_index` selects the address + +**Error codes:** + +| Code | Meaning | +|--------|--------------------------------| +| 0x9000 | Success | +| 0x6985 | User denied | +| 0x6D00 | Unknown instruction | +| 0x6E00 | Wrong CLA | +| 0x6E10 | Signature failed | +| 0x6E01 | Invalid arguments | +| 0x6E02 | Invalid message | +| 0x6E03 | Invalid p1 | +| 0x6E04 | Message too long | +| 0x6E07 | Contract data disabled | +| 0x6E09 | Wrong tx version | +| 0x6E0F | Wrong tx options | +| 0x6E11 | Regular signing is deprecated | + +### `LedgerAccount` (`multiversx_sdk/accounts/ledger_account.py`) + +Wraps `LedgerApp` and exposes `sign_transaction` / `sign_message`. + +- `sign_transaction` requires hash-signing options set: `tx.version >= 2` and `tx.options & 1 == 1`. +- Message signing prepends a 4-byte big-endian length prefix to the message data. + +### CLI (`multiversx_sdk_cli/cli_ledger.py`) + +Two subcommands: +- `ledger addresses [--num-addresses N]` — prints `account_index | address_index | address` for N addresses. +- `ledger version` — prints the app version string (`MAJOR.MINOR.PATCH`). + +--- + +## Rust Crates + +### Option A: `ledger-transport-hid` + `ledger-apdu` (Recommended) + +- **`ledger-apdu`** (crates.io, Zondax): `APDUCommand` and `APDUAnswer` types with proper framing. +- **`ledger-transport-hid`** (crates.io, Zondax): HID transport over `hidapi`. Handles Ledger's 64-byte HID packet framing automatically (non-trivial to implement from scratch). + +These are maintained by Zondax — the same company that maintains the official [MultiversX Ledger app](https://github.com/LedgerHQ/app-multiversx). + +```toml +[dependencies] +ledger-transport-hid = "0.10" +ledger-apdu = "0.10" +``` + +### Option B: `hidapi` directly + +Use the raw `hidapi` crate. Requires manually implementing the Ledger HID framing protocol (64-byte packets with channel/sequence headers). Not recommended — `ledger-transport-hid` does this correctly. + +### Feature flag + +The Ledger crates link against `libhidapi` (a C library; on Linux it also requires `libudev`). This must not be forced on all users of `multiversx-sdk` or `multiversx-sc-snippets`. Gate behind a `ledger` feature in each relevant crate: + +| Crate | Feature | Default | +|--------------------------|-----------|---------| +| `multiversx-sdk` | `ledger` | no | +| `multiversx-sc-snippets` | `ledger` | no | +| `multiversx-sc-meta` | `ledger` | **yes** | + +`sc-meta` enables it by default because it is a standalone developer tool — Ledger support is expected to be available out of the box when running `sc-meta tx sign --ledger` or `sc-meta ledger addresses`. + +CI pipelines running `sc-meta` builds may need `libhidapi-dev` (and `libudev-dev` on Linux) installed. + +--- + +## Testing Without a Physical Device + +### Speculos (Official Ledger Emulator) + +- **Repo:** https://github.com/LedgerHQ/speculos +- Emulates Ledger Nano S/X/S+ using QEMU + ARM cross-compilation. +- Exposes an APDU endpoint on TCP port 9999. +- The `ledger-transport-tcp` crate (Zondax) connects to it. +- **Requires:** the MultiversX Ledger app `.elf` binary (from https://github.com/LedgerHQ/app-multiversx), Python, QEMU. +- Suitable for integration tests in CI if the app binary is bundled. + +### Mock Transport (Unit Tests) + +Abstract the transport behind a trait so unit tests can inject mock responses: + +```rust +pub trait LedgerTransport { + fn exchange(&self, command: &APDUCommand) -> Result; +} + +pub struct LedgerApp { transport: T } +``` + +This allows testing APDU encoding/decoding, chunking logic, error mapping, and response parsing without any device or emulator. + +### Integration Test Strategy + +| Scenario | Approach | +|-------------------------------|--------------------------------------------| +| APDU encoding/chunking | Unit tests with mock transport | +| Response parsing / error codes| Unit tests with mock transport | +| Full sign flow | Integration test against Speculos (CI opt-in, guarded by feature/env var) | +| Physical device smoke test | Manual test only | + +--- + +## Implementation Plan + +### 1. `Signer` enum and `Wallet` restructure (in `sdk/core`) + +Replace the `private_key: PrivateKey` field in `Wallet` with a `signer: Signer` field. + +**`Signer` enum** — always compiled, no `#[cfg]` on the type or its variants: + +```rust +pub enum Signer { + PrivateKey(PrivateKey), + Ledger { address_index: u32 }, +} +``` + +The `Ledger` variant holds only a `u32`, so it has no compile-time dependency on `libhidapi`. The enum is always available, which means `--ledger` flags in configs and CLIs work unconditionally. Runtime signing with `Signer::Ledger` will return an error if the `ledger` feature is absent (see §4). + +**Updated `Wallet` struct:** + +```rust +pub struct Wallet { + pub signer: Signer, // replaces private_key: PrivateKey + pub address: Address, + pub source: WalletSource, +} +``` + +`Wallet::new(private_key, source)` remains for the `PrivateKey` path (address is still derived from the key). A new `Wallet::new_ledger(address, address_index)` constructor is added (always available — it just stores the address and `Signer::Ledger { address_index }`). + +**Impact on existing `Wallet` methods:** + +| Method | Change | +|---------------------|------------------------------------------------------------------------| +| `private_key_hex()` | Returns `Option` — `None` for Ledger | +| `public_key()` | Returns `Option` — `None` for Ledger | +| `public_key_hex()` | Returns `Option` — `None` for Ledger | +| `to_pem()` | Returns `Result` — `Err` for Ledger | +| `sign_tx()` | Dispatches on `Signer`; see §4 | +| `sign_bytes()` | Dispatches on `Signer`; see §4 | + +### 2. `WalletSource::Ledger` (in `sdk/core`) + +Add a new variant to `WalletSource` (always compiled, no `#[cfg]`): + +```rust +pub enum WalletSource { + Mnemonic, + PrivateKey, + PemFile(Bech32Hrp), + TestWallet(&'static str), + Keystore(Bech32Hrp), + Ledger { address_index: u32, hrp: Bech32Hrp }, // NEW +} +``` + +`WalletSource` serves as display / bech32-HRP metadata, consistent with `PemFile` and `Keystore`. `Wallet::to_bech32()` reads `hrp` from the `Ledger` variant just like the other two. The `hrp` is populated from the bech32 address returned by the device when constructing the wallet. + +### 3. `LedgerApp` (in `sdk/core`, feature-gated) + +New module: `sdk/core/src/wallet/ledger/` + +``` +sdk/core/src/wallet/ledger/ + mod.rs — re-exports; entire module gated with #[cfg(feature = "ledger")] + ledger_app.rs — LedgerApp struct, APDU communication + ledger_error.rs — LedgerError enum, error code mapping + ledger_config.rs — LedgerAppConfiguration struct +``` + +**`LedgerApp` API** (only exists when `feature = "ledger"` is active): + +```rust +pub struct LedgerApp { /* HID transport */ } + +impl LedgerApp { + pub fn new() -> Result; + pub fn get_app_configuration(&self) -> Result; + pub fn get_version(&self) -> Result; + pub fn get_address(&self, address_index: u32) -> Result; + pub fn set_address(&self, address_index: u32) -> Result<(), LedgerError>; + pub fn sign_transaction(&self, tx_bytes: &[u8]) -> Result<[u8; 64], LedgerError>; + pub fn sign_message(&self, message_bytes: &[u8]) -> Result<[u8; 64], LedgerError>; +} +``` + +Internally `sign_transaction` and `sign_message` share a `do_sign(data, ins)` helper that: +1. Splits `data` into chunks of at most 150 bytes. +2. Sends APDUs: `p1=0x00` for first chunk, `p1=0x80` for subsequent. +3. Parses the 65-byte response: skips byte 0 (`0x40`), returns bytes 1..=64. + +### 4. Signing dispatch in `Wallet::sign_tx` and `sign_bytes` + +`Wallet::sign_tx` dispatches on `self.signer`: + +```rust +pub fn sign_tx(&self, tx: &Transaction) -> Result { + match &self.signer { + Signer::PrivateKey(pk) => { + // existing logic: serialize → optionally keccak256 → sign + } + Signer::Ledger { address_index } => { + #[cfg(feature = "ledger")] + { + // enforce hash-signing options, open LedgerApp, sign + } + #[cfg(not(feature = "ledger"))] + { + anyhow::bail!( + "Ledger signing is not available; \ + recompile with the `ledger` feature enabled" + ) + } + } + } +} +``` + +The same pattern applies to `sign_bytes`. + +**Transaction signing requirements:** The MultiversX Ledger app requires hash-based signing: +- `tx.version >= 2` and `tx.options & 1 == 1` +- The raw JSON bytes (not the hash) are sent to the device — the device hashes internally. +- If the options are not set, `sign_tx` returns an `Err` (matching Python behaviour). + +### 5. `SenderArgs` extension (in `framework/meta`) + +The new fields are **always** present — no `#[cfg]`: + +```rust +pub struct SenderArgs { + #[arg(long, group = "wallet_source")] + pub pem: Option, + + #[arg(long, group = "wallet_source")] + pub keyfile: Option, + + #[arg(long = "keystore-password")] + pub keystore_password: Option, + + /// Use Ledger hardware wallet for signing. + #[arg(long, group = "wallet_source")] + pub ledger: bool, + + /// Address index to use on the Ledger device (default: 0). + #[arg(long = "ledger-address-index", default_value = "0")] + pub ledger_address_index: u32, +} +``` + +`load_wallet` constructs a `Wallet` with `Signer::Ledger` when `sender.ledger` is true. The address is fetched from the device at that point (requires the `ledger` feature; returns `Err` otherwise). Signing then goes through `Wallet::sign_tx` as normal, which also checks the feature at the point of signing. + +### 6. `WalletConfig` extension (in `framework/snippets`) + +The new fields are **always** present — no `#[cfg]`: + +```rust +pub struct WalletConfig { + pub test_wallet: Option, + pub pem: Option, + pub keyfile: Option, + pub keystore_password: Option, + pub ledger: bool, // NEW + pub ledger_address_index: u32, // NEW (default 0) + cache: OnceLock, +} +``` + +`WalletConfig::load_wallet` adds a branch for `ledger == true` that constructs a `Wallet::new_ledger(...)`. Attempting to use this branch without `feature = "ledger"` produces the same runtime error as §4. + +### 7. `sc-meta ledger` CLI subcommand + +New variant in `StandaloneCliAction`: + +```rust +#[command(name = "ledger", about = "Interact with a Ledger hardware wallet.")] +Ledger(LedgerArgs), +``` + +New file: `framework/meta/src/cli/cli_args_ledger.rs` + +```rust +#[derive(Args)] +pub struct LedgerArgs { + #[command(subcommand)] + pub command: LedgerAction, +} + +#[derive(Subcommand)] +pub enum LedgerAction { + #[command(about = "List addresses stored on the Ledger device.")] + Addresses(LedgerAddressesArgs), + + #[command(about = "Print the version of the MultiversX Ledger app.")] + Version, +} + +#[derive(Args)] +pub struct LedgerAddressesArgs { + /// Number of addresses to retrieve (default: 10). + #[arg(long = "num-addresses", default_value = "10")] + pub num_addresses: u32, +} +``` + +The `sc-meta ledger` subcommand handlers live in `framework/meta/src/cmd/ledger_cmd.rs` and are **always compiled** — no `#[cfg]` on the functions or the CLI types. Only the inner calls to `LedgerApp::new()` are guarded, returning a clear error when the `ledger` feature is absent (same pattern as `Wallet::sign_tx` in §4). Since `sc-meta` enables the `ledger` feature by default, the guard is never hit in normal usage. + +Output format (matching Python): +``` +account index = 0 | address index = 0 | address: erd1... +account index = 0 | address index = 1 | address: erd1... +``` + +--- + +## File Structure Summary + +``` +sdk/core/src/wallet/ + signer.rs — NEW: Signer enum (PrivateKey | Ledger) + wallet_source.rs — add Ledger { address_index: u32 } variant + wallet_impl.rs — replace private_key field; update sign_tx/sign_bytes + ledger/ + mod.rs — NEW: #[cfg(feature = "ledger")] + ledger_app.rs — NEW: LedgerApp, APDU protocol, chunked signing + ledger_config.rs — NEW: LedgerAppConfiguration + ledger_error.rs — NEW: LedgerError, error code mapping +sdk/core/Cargo.toml — add ledger-transport-hid, ledger-apdu as optional deps + +framework/snippets/src/config/wallet_config.rs — add ledger / ledger_address_index fields +framework/snippets/Cargo.toml — add ledger feature forwarding to multiversx-sdk + +framework/meta/src/cli/cli_args_ledger.rs — NEW: LedgerArgs, LedgerAction, LedgerAddressesArgs +framework/meta/src/cli/cli_args_sender.rs — add --ledger / --ledger-address-index flags +framework/meta/src/cli/cli_args_standalone.rs — add Ledger subcommand +framework/meta/src/cmd/ledger_cmd.rs — NEW: CLI command handlers +framework/meta/Cargo.toml — ledger feature default; forward to multiversx-sdk +``` + +--- + +## Cargo.toml Changes + +**`sdk/core/Cargo.toml`:** + +```toml +[features] +ledger = ["dep:ledger-transport-hid", "dep:ledger-apdu"] + +[dependencies] +ledger-transport-hid = { version = "0.10", optional = true } +ledger-apdu = { version = "0.10", optional = true } +``` + +**`framework/snippets/Cargo.toml`:** + +```toml +[features] +ledger = ["multiversx-sdk/ledger"] +``` + +**`framework/meta/Cargo.toml`:** + +```toml +[features] +default = ["ledger"] +ledger = ["multiversx-sdk/ledger"] +``` + +> **Note on `libhidapi`:** `ledger-transport-hid` links against `hidapi` (a C library). On Linux it also requires `libudev`. CI pipelines building `sc-meta` (which enables `ledger` by default) may need `libhidapi-dev` / `libudev-dev` installed. + + diff --git a/framework/meta/src/cli.rs b/framework/meta/src/cli.rs index bfbddd6d30..9f98ed4a61 100644 --- a/framework/meta/src/cli.rs +++ b/framework/meta/src/cli.rs @@ -1,3 +1,4 @@ +pub mod cli_args_ledger; pub mod cli_args_reproducible_builds; pub mod cli_args_sender; mod cli_args_standalone; @@ -5,6 +6,7 @@ pub mod cli_args_tx; mod cli_args_validate; mod cli_standalone_main; +pub use cli_args_ledger::*; pub use cli_args_standalone::*; pub use cli_args_validate::*; pub use cli_standalone_main::*; diff --git a/framework/meta/src/cli/cli_args_ledger.rs b/framework/meta/src/cli/cli_args_ledger.rs new file mode 100644 index 0000000000..f04461a4d2 --- /dev/null +++ b/framework/meta/src/cli/cli_args_ledger.rs @@ -0,0 +1,24 @@ +use clap::{Args, Subcommand}; + +/// Arguments for the `sc-meta ledger` subcommand group. +#[derive(Clone, PartialEq, Eq, Debug, Args)] +pub struct LedgerArgs { + #[command(subcommand)] + pub command: LedgerAction, +} + +#[derive(Clone, PartialEq, Eq, Debug, Subcommand)] +pub enum LedgerAction { + #[command(about = "List addresses stored on the Ledger device.")] + Addresses(LedgerAddressesArgs), + + #[command(about = "Print the version of the MultiversX Ledger app.")] + Version, +} + +#[derive(Clone, PartialEq, Eq, Debug, Args)] +pub struct LedgerAddressesArgs { + /// Number of addresses to retrieve. + #[arg(long = "num-addresses", default_value = "10")] + pub num_addresses: u32, +} diff --git a/framework/meta/src/cli/cli_args_sender.rs b/framework/meta/src/cli/cli_args_sender.rs index 15518b1338..a05f485dac 100644 --- a/framework/meta/src/cli/cli_args_sender.rs +++ b/framework/meta/src/cli/cli_args_sender.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result, anyhow}; use clap::Args; -use multiversx_sc_snippets::sdk::{data::keystore::InsertPassword, wallet::Wallet}; -use std::path::PathBuf; +use multiversx_sc_snippets::sdk::{wallet::Keystore, wallet::Wallet}; +use std::{io::Write, path::PathBuf}; /// Wallet / sender arguments shared by commands that sign transactions. #[derive(Clone, PartialEq, Eq, Debug, Args)] @@ -17,6 +17,38 @@ pub struct SenderArgs { /// Keystore password (plain text). If omitted, will prompt interactively. #[arg(long = "keystore-password", verbatim_doc_comment)] pub keystore_password: Option, + + /// Use a Ledger hardware wallet for signing. + #[arg(long, group = "wallet_source")] + pub ledger: bool, + + /// Address index to use on the Ledger device. + #[arg(long, default_value = "0")] + pub sender_wallet_index: u32, +} + +/// Load a wallet from a PEM file, JSON keystore, or Ledger device. +/// Wallet arguments for the relayer. Mirrors [`SenderArgs`] with a `--relayer-*` prefix. +#[derive(Clone, PartialEq, Eq, Debug, Args)] +pub struct RelayerArgs { + /// Bech32 address of the relayer that will pay the gas fees (relayed v3). + /// The gas limit must include the extra base cost for the relayed operation. + /// Allows specifying a relayer address without a wallet, + /// in which case the transaction will remain unsigned by the relayer. + #[arg(long)] + pub relayer: Option, + + /// Path to a PEM wallet file for the relayer. + #[arg(long = "relayer-pem", group = "relayer_source")] + pub relayer_pem: Option, + + /// Path to a JSON keystore wallet file for the relayer. + #[arg(long = "relayer-keyfile", group = "relayer_source")] + pub relayer_keyfile: Option, + + /// Relayer keystore password (plain text). If omitted, will prompt interactively. + #[arg(long = "relayer-keystore-password")] + pub relayer_keystore_password: Option, } /// Load a wallet from a PEM file or JSON keystore. @@ -24,13 +56,66 @@ pub fn load_wallet(sender: &SenderArgs) -> Result { if let Some(pem) = &sender.pem { Wallet::from_pem_file(pem).context("failed to load PEM wallet") } else if let Some(keyfile) = &sender.keyfile { - let insert_password = match &sender.keystore_password { - Some(pw) => InsertPassword::Plaintext(pw.clone()), - None => InsertPassword::StandardInput, + let password = match &sender.keystore_password { + Some(pw) => pw.clone(), + None => get_keystore_password(), }; - Wallet::from_keystore_secret(keyfile, insert_password) + let keystore = Keystore::from_file(keyfile)?; + keystore + .decrypt_wallet(&password) .context("failed to load keystore wallet") + } else if sender.ledger { + load_ledger_wallet(sender.sender_wallet_index) } else { - Err(anyhow!("a wallet is required: use --pem or --keyfile")) + Err(anyhow!( + "a wallet is required: use --pem, --keyfile, or --ledger" + )) } } + +#[cfg(feature = "ledger")] +fn load_ledger_wallet(address_index: u32) -> Result { + use multiversx_chain_core::std::Bech32Address; + use multiversx_sc_snippets::sdk::wallet::ledger::LedgerApp; + + let mut app = LedgerApp::new().map_err(|e| anyhow!("{e}"))?; + let bech32_str = app.get_address(address_index).map_err(|e| anyhow!("{e}"))?; + let bech32_addr = Bech32Address::from_bech32_string(bech32_str); + let hrp = bech32_addr.hrp; + let address = bech32_addr.address; + Ok(Wallet::new_ledger(address, hrp, address_index)) +} + +#[cfg(not(feature = "ledger"))] +fn load_ledger_wallet(address_index: u32) -> Result { + let _ = address_index; + Err(anyhow!( + "Ledger support is not available; recompile with the `ledger` feature enabled" + )) +} + +/// Load a relayer wallet from [`RelayerArgs`], returning `None` if no relayer args are set. +pub fn load_relayer_wallet(args: &RelayerArgs) -> Result> { + if let Some(pem) = &args.relayer_pem { + let wallet = Wallet::from_pem_file(pem).context("failed to load relayer PEM wallet")?; + Ok(Some(wallet)) + } else if let Some(keyfile) = &args.relayer_keyfile { + let password = match &args.relayer_keystore_password { + Some(pw) => pw.clone(), + None => get_keystore_password(), + }; + let keystore = Keystore::from_file(keyfile)?; + let wallet = keystore + .decrypt_wallet(&password) + .context("failed to load relayer keystore wallet")?; + Ok(Some(wallet)) + } else { + Ok(None) + } +} + +pub fn get_keystore_password() -> String { + print!("Insert password: "); + std::io::stdout().flush().unwrap(); + rpassword::read_password().unwrap() +} diff --git a/framework/meta/src/cli/cli_args_standalone.rs b/framework/meta/src/cli/cli_args_standalone.rs index fb2ea896d7..2d3e59c27f 100644 --- a/framework/meta/src/cli/cli_args_standalone.rs +++ b/framework/meta/src/cli/cli_args_standalone.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; pub use super::cli_args_reproducible_builds::*; +use crate::cli::cli_args_ledger::LedgerArgs; use crate::cli::cli_args_tx::TxCliArgs; use multiversx_sc_meta_lib::cli::{CliArgsToRaw, ContractCliAction}; @@ -43,7 +44,7 @@ pub enum StandaloneCliAction { #[command( about = "Calls the meta crates for all contracts under given path with the given arguments." )] - All(AllArgs), + All(Box), #[command( about = "Upgrades a contract to the latest version. Multiple contract crates are allowed." @@ -111,7 +112,13 @@ pub enum StandaloneCliAction { name = "tx", about = "Deploy, call, upgrade, query contracts or create/send/sign transactions." )] - Tx(TxCliArgs), + Tx(Box), + + #[command( + name = "ledger", + about = "Interact with a Ledger hardware wallet (list addresses, get app version)." + )] + Ledger(LedgerArgs), #[command( name = "data", diff --git a/framework/meta/src/cli/cli_args_tx.rs b/framework/meta/src/cli/cli_args_tx.rs index 6894a299dc..bffc52b8ee 100644 --- a/framework/meta/src/cli/cli_args_tx.rs +++ b/framework/meta/src/cli/cli_args_tx.rs @@ -1,7 +1,7 @@ use clap::{Args, Subcommand}; use std::path::PathBuf; -pub use super::cli_args_sender::SenderArgs; +pub use super::cli_args_sender::{RelayerArgs, SenderArgs}; #[derive(Clone, PartialEq, Eq, Debug, Args)] pub struct TxCliArgs { @@ -31,6 +31,11 @@ pub enum TxCliAction { #[command(about = "Signs an unsigned transaction from a file.")] Sign(SignArgs), + + #[command( + about = "Adds the relayer signature to a previously signed transaction (relayed v3)." + )] + Relay(RelayArgs), } /// Gateway / network arguments shared by commands that talk to the blockchain. @@ -130,6 +135,9 @@ pub struct DeployArgs { #[command(flatten)] pub sender: SenderArgs, + #[command(flatten)] + pub relayer: RelayerArgs, + #[command(flatten)] pub tx: TxArgs, @@ -159,6 +167,9 @@ pub struct CallArgs { #[command(flatten)] pub sender: SenderArgs, + #[command(flatten)] + pub relayer: RelayerArgs, + #[command(flatten)] pub tx: TxArgs, @@ -189,6 +200,9 @@ pub struct UpgradeArgs { #[command(flatten)] pub sender: SenderArgs, + #[command(flatten)] + pub relayer: RelayerArgs, + #[command(flatten)] pub tx: TxArgs, @@ -236,6 +250,9 @@ pub struct NewArgs { #[command(flatten)] pub sender: SenderArgs, + #[command(flatten)] + pub relayer: RelayerArgs, + #[command(flatten)] pub tx: TxArgs, @@ -283,6 +300,36 @@ pub struct SignArgs { #[command(flatten)] pub sender: SenderArgs, + #[command(flatten)] + pub relayer: RelayerArgs, + #[command(flatten)] pub gateway: GatewayArgs, } + +/// Arguments for the `tx relay` command: sign a transaction as the relayer. +#[derive(Clone, PartialEq, Eq, Debug, Args)] +pub struct RelayArgs { + /// Path to the input transaction JSON file. + /// Must already contain the sender signature and the `relayer` field. + #[arg(long)] + pub infile: PathBuf, + + #[command(flatten)] + pub relayer: RelayerArgs, + + #[command(flatten)] + pub gateway: GatewayArgs, + + /// If set, the transaction is broadcast after signing. + #[arg(long, default_value = "false")] + pub send: bool, + + /// Wait for the transaction result. Requires --send. + #[arg(long, default_value = "false", requires = "send")] + pub wait_result: bool, + + /// Path to write the output to. Defaults to stdout. + #[arg(long)] + pub outfile: Option, +} diff --git a/framework/meta/src/cli/cli_standalone_main.rs b/framework/meta/src/cli/cli_standalone_main.rs index 0218e02a7d..5e19b7a6a2 100644 --- a/framework/meta/src/cli/cli_standalone_main.rs +++ b/framework/meta/src/cli/cli_standalone_main.rs @@ -1,10 +1,11 @@ use crate::cli::{ReproducibleBuildCliAction, StandaloneCliAction, StandaloneCliArgs}; use crate::cmd::chain_simulator::chain_simulator; use crate::cmd::data::data_cli; +use crate::cmd::ledger_cmd::ledger_cmd; use crate::cmd::retrieve_address::retrieve_address; use crate::cmd::scen_blackbox::scen_blackbox_tool; -use crate::cmd::tx::tx_cli; -use crate::cmd::wallet::wallet; +use crate::cmd::tx_cli::tx_cli; +use crate::cmd::wallet_cmd::wallet; use clap::Parser; use crate::cmd::all::call_all_meta; @@ -90,6 +91,9 @@ pub async fn cli_main_standalone() { Some(StandaloneCliAction::Tx(args)) => { tx_cli(args).await; } + Some(StandaloneCliAction::Ledger(args)) => { + ledger_cmd(args); + } Some(StandaloneCliAction::Data(args)) => { data_cli(args); } diff --git a/framework/meta/src/cmd.rs b/framework/meta/src/cmd.rs index e01fa4d5d5..81651c4712 100644 --- a/framework/meta/src/cmd.rs +++ b/framework/meta/src/cmd.rs @@ -5,6 +5,7 @@ pub mod code_report; pub mod data; pub mod info; pub mod install; +pub mod ledger_cmd; pub mod print_util; pub mod reproducible_builds; pub mod retrieve_address; @@ -13,6 +14,6 @@ pub mod scen_test_gen; pub mod template; pub mod test; pub mod test_coverage; -pub mod tx; +pub mod tx_cli; pub mod upgrade; -pub mod wallet; +pub mod wallet_cmd; diff --git a/framework/meta/src/cmd/ledger_cmd.rs b/framework/meta/src/cmd/ledger_cmd.rs new file mode 100644 index 0000000000..3819c466c7 --- /dev/null +++ b/framework/meta/src/cmd/ledger_cmd.rs @@ -0,0 +1,47 @@ +use crate::cli::{LedgerAction, LedgerArgs}; + +pub fn ledger_cmd(args: &LedgerArgs) { + if let Err(e) = ledger_cmd_inner(args) { + eprintln!("Error: {e:#}"); + std::process::exit(1); + } +} + +fn ledger_cmd_inner(args: &LedgerArgs) -> anyhow::Result<()> { + match &args.command { + LedgerAction::Addresses(a) => print_addresses(a.num_addresses), + LedgerAction::Version => print_version(), + } +} + +#[cfg(feature = "ledger")] +fn print_addresses(num_addresses: u32) -> anyhow::Result<()> { + use multiversx_sc_snippets::sdk::wallet::ledger::LedgerApp; + + let mut app = LedgerApp::new()?; + for i in 0..num_addresses { + let address = app.get_address(i)?; + println!("account index = 0 | address index = {i} | address: {address}"); + } + Ok(()) +} + +#[cfg(not(feature = "ledger"))] +fn print_addresses(_num_addresses: u32) -> anyhow::Result<()> { + anyhow::bail!("Ledger support is not available; recompile with the `ledger` feature enabled") +} + +#[cfg(feature = "ledger")] +fn print_version() -> anyhow::Result<()> { + use multiversx_sc_snippets::sdk::wallet::ledger::LedgerApp; + + let mut app = LedgerApp::new()?; + let version = app.get_version()?; + println!("MultiversX App version: {version}"); + Ok(()) +} + +#[cfg(not(feature = "ledger"))] +fn print_version() -> anyhow::Result<()> { + anyhow::bail!("Ledger support is not available; recompile with the `ledger` feature enabled") +} diff --git a/framework/meta/src/cmd/print_util.rs b/framework/meta/src/cmd/print_util.rs index d0895cdf87..432dd6d089 100644 --- a/framework/meta/src/cmd/print_util.rs +++ b/framework/meta/src/cmd/print_util.rs @@ -43,6 +43,9 @@ pub fn print_tree_dir_metadata(dir: &RelevantDirectory, last_version: &Framework print!(" {}", version_string.red()); }; } + DependencyReference::Workspace => { + print!(" {}", "[workspace]".green()); + } DependencyReference::GitCommit(git_reference) => { let git_string = format!( "[git: {} rev: {}]", diff --git a/framework/meta/src/cmd/reproducible_builds/publish.rs b/framework/meta/src/cmd/reproducible_builds/publish.rs index 0eccefdad4..c463e5f05d 100644 --- a/framework/meta/src/cmd/reproducible_builds/publish.rs +++ b/framework/meta/src/cmd/reproducible_builds/publish.rs @@ -4,7 +4,7 @@ use std::time::Duration; use hex::ToHex; use multiversx_chain_core::std::crypto; use multiversx_sc_snippets::imports::Bech32Address; -use multiversx_sc_snippets::sdk::wallet::Wallet; +use multiversx_sc_snippets::sdk::wallet::{Wallet, WalletSignature}; use crate::cli::ReproducibleBuildPublishArgs; use crate::cli::cli_args_sender::load_wallet; @@ -52,12 +52,12 @@ pub async fn publish_contract(args: &ReproducibleBuildPublishArgs) { ); // Sign the payload. - let signature_hex = sign_payload(&wallet, &contract, &payload); + let signature = sign_payload(&wallet, &contract, &payload); let url = format!("{}/verifier", args.verifier_url.trim_end_matches('/')); let (status, body) = post_verification_request( &url, - &signature_hex, + signature, &contract, &args.docker_image, &source_code, @@ -106,17 +106,22 @@ pub(super) fn compute_bytes_for_signing(contract: &Bech32Address, payload: &str) crypto::keccak256(&content).to_vec() } -/// Signs the payload using the MultiversX message signing protocol and returns the hex signature. -pub(super) fn sign_payload(wallet: &Wallet, contract: &Bech32Address, payload: &str) -> String { +/// Signs the payload using the MultiversX message signing protocol and returns the signature. +pub(super) fn sign_payload( + wallet: &Wallet, + contract: &Bech32Address, + payload: &str, +) -> WalletSignature { let bytes_to_sign = compute_bytes_for_signing(contract, payload); - let signature: [u8; 64] = wallet.sign_bytes(bytes_to_sign); - signature.encode_hex() + wallet + .sign_bytes(bytes_to_sign) + .expect("failed to sign payload") } /// POSTs the verification request and returns the HTTP status code and parsed response body. async fn post_verification_request( url: &str, - signature: &str, + signature: WalletSignature, contract: &Bech32Address, docker_image: &str, source_code: &serde_json::Value, @@ -228,7 +233,6 @@ async fn poll_task(verifier_url: &str, task_id: &str) { #[cfg(test)] mod tests { use super::*; - use hex::ToHex; use multiversx_chain_core::std::crypto::{keccak256, sha256}; /// Independently re-implements the signing bytes computation and checks it matches. diff --git a/framework/meta/src/cmd/reproducible_builds/source_pack.rs b/framework/meta/src/cmd/reproducible_builds/source_pack.rs index 2650d5ff0f..8fc5c2f77d 100644 --- a/framework/meta/src/cmd/reproducible_builds/source_pack.rs +++ b/framework/meta/src/cmd/reproducible_builds/source_pack.rs @@ -1,4 +1,4 @@ -use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; +use multiversx_chain_core::std::base64_encode; use serde::Serialize; use std::{ collections::HashSet, @@ -165,7 +165,7 @@ fn make_entry( ) -> SourceFileEntry { let rel = pathdiff::diff_paths(file, project_folder).unwrap(); let path_str = rel.to_string_lossy().replace('\\', "/"); - let content = BASE64.encode(fs::read(file).unwrap()); + let content = base64_encode(fs::read(file).unwrap()); SourceFileEntry { path: path_str.clone(), content, diff --git a/framework/meta/src/cmd/reproducible_builds/source_unpack.rs b/framework/meta/src/cmd/reproducible_builds/source_unpack.rs index eb56248355..3a3bad2ed9 100644 --- a/framework/meta/src/cmd/reproducible_builds/source_unpack.rs +++ b/framework/meta/src/cmd/reproducible_builds/source_unpack.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result, bail}; -use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; +use multiversx_chain_core::std::base64_decode; use std::{ fs, path::{Component, Path, PathBuf}, @@ -67,8 +67,7 @@ pub fn unpack_packed_source( fs::create_dir_all(parent) .with_context(|| format!("Failed to create {}", parent.display()))?; } - let content = BASE64 - .decode(&entry.content) + let content = base64_decode(&entry.content) .with_context(|| format!("Failed to decode entry '{}'", entry.path))?; fs::write(&file_path, content) .with_context(|| format!("Failed to write {}", file_path.display()))?; diff --git a/framework/meta/src/cmd/reproducible_builds/unpublish.rs b/framework/meta/src/cmd/reproducible_builds/unpublish.rs index 99ba434fea..a799138662 100644 --- a/framework/meta/src/cmd/reproducible_builds/unpublish.rs +++ b/framework/meta/src/cmd/reproducible_builds/unpublish.rs @@ -1,6 +1,5 @@ use std::io::{self, Write}; -use hex::ToHex; use multiversx_sc_snippets::imports::Bech32Address; use crate::cli::ReproducibleBuildUnpublishArgs; @@ -38,11 +37,10 @@ pub async fn unpublish_contract(args: &ReproducibleBuildUnpublishArgs) { // Sign using the same MultiversX message signing protocol as verify. let bytes_to_sign = compute_bytes_for_signing(&contract, &payload); - let signature: [u8; 64] = wallet.sign_bytes(bytes_to_sign); - let signature_hex: String = signature.encode_hex(); + let signature = wallet.sign_bytes(bytes_to_sign).expect("failed to sign"); let request_body = serde_json::json!({ - "signature": signature_hex, + "signature": signature, "payload": payload_obj, }); diff --git a/framework/meta/src/cmd/template/contract_creator.rs b/framework/meta/src/cmd/template/contract_creator.rs index 0144b474a3..936fb62c82 100644 --- a/framework/meta/src/cmd/template/contract_creator.rs +++ b/framework/meta/src/cmd/template/contract_creator.rs @@ -1,4 +1,5 @@ use crate::{cli::TemplateArgs, version::FrameworkVersion, version_history::LAST_TEMPLATE_VERSION}; +use multiversx_sc_meta_lib::cargo_toml::WorkspaceDependencies; use super::{ ContractCreatorTarget, RepoSource, RepoVersion, TemplateAdjuster, @@ -77,6 +78,8 @@ impl<'a> ContractCreator<'a> { .unwrap_or_else(|| panic!("Unknown template {template_name}")); let metadata = template_source.metadata.clone(); + let workspace_dependencies = WorkspaceDependencies::load_from_dir(repo_source.repo_path()); + ContractCreator { repo_source, template_source, @@ -86,12 +89,14 @@ impl<'a> ContractCreator<'a> { target, keep_paths, new_author, + workspace_dependencies, }, } } pub fn create_contract(&self, args_tag: FrameworkVersion) { self.copy_template(args_tag); + self.resolve_workspace_dependencies(); self.update_dependencies(); self.rename_template(); } @@ -105,6 +110,10 @@ impl<'a> ContractCreator<'a> { self.adjuster.update_cargo_toml_files(); } + pub fn resolve_workspace_dependencies(&self) { + self.adjuster.resolve_workspace_dependencies(); + } + pub fn rename_template(&self) { self.adjuster.rename_template_to(); } diff --git a/framework/meta/src/cmd/template/template_adjuster.rs b/framework/meta/src/cmd/template/template_adjuster.rs index bf82c791b6..a9d10ce430 100644 --- a/framework/meta/src/cmd/template/template_adjuster.rs +++ b/framework/meta/src/cmd/template/template_adjuster.rs @@ -1,9 +1,9 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use super::{ContractCreatorTarget, template_metadata::TemplateMetadata}; use crate::cmd::upgrade::upgrade_common::{rename_files, replace_in_files}; use convert_case::{Case, Casing}; -use multiversx_sc_meta_lib::cargo_toml::CargoTomlContents; +use multiversx_sc_meta_lib::cargo_toml::{CargoTomlContents, WorkspaceDependencies}; use ruplacer::Query; use toml::value::Table; @@ -16,8 +16,21 @@ pub struct TemplateAdjuster { pub target: ContractCreatorTarget, pub keep_paths: bool, pub new_author: Option, + pub workspace_dependencies: WorkspaceDependencies, } impl TemplateAdjuster { + pub fn resolve_workspace_dependencies(&self) { + for cargo_toml_path in self.cargo_toml_paths() { + if !cargo_toml_path.exists() { + continue; + } + + let mut toml = CargoTomlContents::load_from_file(&cargo_toml_path); + toml.resolve_workspace_dependencies(&self.workspace_dependencies); + toml.save_to_file(&cargo_toml_path); + } + } + pub fn update_cargo_toml_files(&self) { let author_as_str = self .new_author @@ -29,6 +42,23 @@ impl TemplateAdjuster { self.update_cargo_toml_interact(author_as_str); } + fn cargo_toml_paths(&self) -> Vec { + let mut paths = vec![ + self.target.contract_dir().join(CARGO_TOML), + self.target.contract_dir().join("meta").join(CARGO_TOML), + self.target.contract_dir().join("wasm").join(CARGO_TOML), + ]; + if self.metadata.has_interactor { + paths.push( + self.target + .contract_dir() + .join("interactor") + .join(CARGO_TOML), + ); + } + paths + } + fn update_cargo_toml_root(&self, author: String) { let cargo_toml_path = self.target.contract_dir().join(CARGO_TOML); let mut toml = CargoTomlContents::load_from_file(&cargo_toml_path); @@ -101,7 +131,6 @@ impl TemplateAdjuster { let old_snake = self.metadata.name.to_case(Case::Snake); let new_trait = self.target.new_name.to_case(Case::UpperCamel); let old_trait = &self.metadata.contract_trait; - let new_src_file = rs_file_name(&new_snake); let old_wasm = wasm_file_name(&self.metadata.name); let new_wasm = wasm_file_name(&self.target.new_name); let old_mxsc = mxsc_file_name(&self.metadata.name); @@ -119,6 +148,10 @@ impl TemplateAdjuster { Query::simple(old_trait, &new_trait), Query::simple(&format!("{old_snake}::"), &format!("{new_snake}::")), Query::simple(&format!("{old_snake}_proxy"), &format!("{new_snake}_proxy")), + Query::simple( + &format!("{old_snake}_interactor"), + &format!("{new_snake}_interactor"), + ), Query::simple( &as_path(&self.metadata.name), &as_path(&self.target.new_name), @@ -146,7 +179,15 @@ impl TemplateAdjuster { &package_name_expr(&self.metadata.name), &package_name_expr(&self.target.new_name), ), - Query::simple(&self.metadata.src_file, &new_src_file), + Query::simple(&self.metadata.src_file, &rs_file_name(&new_snake)), + Query::simple( + &format!("{old_snake}_interactor.rs"), + &format!("{new_snake}_interactor.rs"), + ), + Query::simple( + &format!("{old_snake}_interactor_main.rs"), + &format!("{new_snake}_interactor_main.rs"), + ), Query::simple( &package_name_expr(&format!("{}-meta", self.metadata.name)), &package_name_expr(&format!("{}-meta", self.target.new_name)), @@ -159,6 +200,10 @@ impl TemplateAdjuster { &package_name_expr(&format!("{}-wasm", self.metadata.name)), &package_name_expr(&format!("{}-wasm", self.target.new_name)), ), + Query::simple( + &package_name_expr(&format!("{}-interactor", self.metadata.name)), + &package_name_expr(&format!("{}-interactor", self.target.new_name)), + ), ], ); diff --git a/framework/meta/src/cmd/tx/output.rs b/framework/meta/src/cmd/tx/output.rs deleted file mode 100644 index 7476f70383..0000000000 --- a/framework/meta/src/cmd/tx/output.rs +++ /dev/null @@ -1,31 +0,0 @@ -use multiversx_sc_snippets::sdk::data::transaction::{ApiTransactionResult, Transaction}; -use serde::{Deserialize, Serialize}; - -/// mxpy-compatible output format for a signed transaction. -/// -/// When saved to a file this can be re-loaded by `sc-meta tx send --infile ` -/// or by `mxpy tx send --infile `. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TxOutputFile { - #[serde(rename = "emittedTransaction")] - pub emitted_transaction: Transaction, - - /// Decoded (plain-text / UTF-8) representation of the transaction data field. - #[serde(rename = "emittedTransactionData")] - pub emitted_transaction_data: String, - - /// Populated after a successful broadcast; empty string when just serializing. - #[serde(rename = "emittedTransactionHash")] - pub emitted_transaction_hash: String, - - /// Populated for deploy transactions: the deterministically-computed contract address. - #[serde(rename = "contractAddress", skip_serializing_if = "Option::is_none")] - pub contract_address: Option, - - /// Populated after waiting for the transaction result on-network. - #[serde( - rename = "transactionOnNetwork", - skip_serializing_if = "Option::is_none" - )] - pub transaction_on_network: Option, -} diff --git a/framework/meta/src/cmd/tx/tx_cli_common.rs b/framework/meta/src/cmd/tx/tx_cli_common.rs deleted file mode 100644 index 5b8c21edbb..0000000000 --- a/framework/meta/src/cmd/tx/tx_cli_common.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::fs; - -use anyhow::{Context, Result, anyhow}; -use multiversx_sc_snippets::ExplorerUrl; -use multiversx_sc_snippets::{ - hex, - imports::{ - BytesValue, GatewayHttpProxy, InterpretableFrom, InterpreterContext, ManagedArgBuffer, - ManagedBuffer, StaticApi, - }, - sdk::{ - data::transaction::{ApiTransactionResult, Transaction}, - utils::base64_decode, - wallet::Wallet, - }, -}; -use serde::Serialize; - -use multiversx_sc_scenario::imports::ReturnCode; -use multiversx_sc_snippets::network_response; -use serde_json::Value; - -use super::output::TxOutputFile; -pub use crate::cli::cli_args_sender::load_wallet; -use crate::cli::cli_args_tx::{GatewayArgs, TxArgs}; - -/// Load a transaction from an mxpy-compatible interaction JSON file. -/// Accepts both `{"emittedTransaction": {...}}` and `{"tx": {...}}` wrappers. -pub(super) fn load_transaction_from_file(path: &std::path::Path) -> Result { - let content = - fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?; - let v: Value = serde_json::from_str(&content) - .with_context(|| format!("invalid JSON in {}", path.display()))?; - - let tx_value = v - .get("emittedTransaction") - .or_else(|| v.get("tx")) - .ok_or_else(|| { - anyhow!( - "file {} must contain an \"emittedTransaction\" or \"tx\" key", - path.display() - ) - })?; - - serde_json::from_value(tx_value.clone()) - .with_context(|| format!("failed to deserialize transaction from {}", path.display())) -} - -/// Wait for a transaction result on the network. -pub(super) async fn fetch_tx_on_network( - gateway: &str, - tx_hash: &str, -) -> Result<(ApiTransactionResult, ReturnCode)> { - let proxy = GatewayHttpProxy::new(gateway.to_string()); - multiversx_sdk::retrieve_tx_on_network(&proxy, tx_hash.to_string()).await -} - -/// Write `output` to `outfile`, or print to stdout when no outfile is given. -pub(super) fn save_output(output: &TxOutputFile, outfile: Option<&std::path::Path>) -> Result<()> { - let json = to_json_pretty(output)?; - if let Some(path) = outfile { - fs::write(path, &json).with_context(|| format!("failed to write to {}", path.display()))?; - println!("Transaction saved to {}", path.display()); - } else { - println!("{json}"); - } - Ok(()) -} - -/// Broadcast the transaction inside `output`, update the hash (and optionally -/// the on-network result), then write/print the updated output. -pub(super) async fn broadcast_and_save( - output: TxOutputFile, - proxy_url: &str, - outfile: Option<&std::path::Path>, - wait_result: bool, -) -> Result<()> { - if output.emitted_transaction.signature.is_none() { - return Err(anyhow!( - "transaction is not signed; sign it before broadcasting" - )); - } - - let proxy = GatewayHttpProxy::new(proxy_url.to_string()); - let tx_hash = proxy - .send_transaction(&output.emitted_transaction) - .await - .context("failed to broadcast transaction")?; - if let Some(ex) = ExplorerUrl::from_chain_id(&output.emitted_transaction.chain_id) { - println!("transaction: {}", ex.tx_url(&tx_hash)); - } else { - println!("transaction hash: {tx_hash}"); - } - - let mut output_with_hash = TxOutputFile { - emitted_transaction_hash: tx_hash.clone(), - ..output - }; - - if wait_result { - println!("Waiting for transaction result..."); - let (tx_on_network, return_code) = fetch_tx_on_network(proxy_url, &tx_hash).await?; - let tx_response = network_response::parse_tx_response(tx_on_network.clone(), return_code); - print_tx_results(&tx_response); - output_with_hash.transaction_on_network = Some(tx_on_network); - } - - let json = to_json_pretty(&output_with_hash)?; - if let Some(path) = outfile { - fs::write(path, &json).with_context(|| format!("failed to write to {}", path.display()))?; - } else { - println!("{json}"); - } - Ok(()) -} - -/// Serialize a value to a JSON string with 4-space indentation (matches mxpy output). -pub(super) fn to_json_pretty(value: &T) -> Result { - let mut buf = Vec::new(); - let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter); - value - .serialize(&mut ser) - .context("failed to serialize transaction")?; - String::from_utf8(buf).context("non-UTF8 in serialized JSON") -} - -/// Interpret a list of mandos-format argument strings (e.g. `0x1a`, `str:hello`, `42`) -/// into a `ManagedArgBuffer` ready to be passed to `.arguments_raw()`. -pub fn build_arg_buffer(arguments: &[String]) -> Result> { - let context = - InterpreterContext::new().with_dir(std::env::current_dir().context("failed to get cwd")?); - let mut arg_buffer = ManagedArgBuffer::::new(); - for s in arguments { - let bv = BytesValue::interpret_from(s.as_str(), &context); - arg_buffer.push_arg_raw(ManagedBuffer::new_from_bytes(&bv.value)); - } - Ok(arg_buffer) -} - -/// Apply nonce / gas-price / chain-id overrides, sign the transaction, then -/// write / print / broadcast it according to the `TxArgs` flags. -/// `contract_address` should be `Some(bech32)` for deploy transactions. -pub async fn sign_and_dispatch( - wallet: Wallet, - mut tx: Transaction, - nonce: u64, - tx_args: &TxArgs, - gateway_args: &GatewayArgs, - contract_address: Option, -) -> Result<()> { - tx.nonce = nonce; - if let Some(gas_price) = tx_args.gas_price { - tx.gas_price = gas_price; - } - if let Some(chain_id) = &gateway_args.chain { - tx.chain_id = chain_id.clone(); - } - - let decoded_data = tx - .data - .as_ref() - .map(|d| String::from_utf8_lossy(&base64_decode(d)).into_owned()) - .unwrap_or_default(); - - let sig = wallet.sign_tx(&tx); - tx.signature = Some(hex::encode(sig)); - - let output = TxOutputFile { - emitted_transaction: tx, - emitted_transaction_data: decoded_data, - emitted_transaction_hash: String::new(), - contract_address, - transaction_on_network: None, - }; - - if tx_args.send { - broadcast_and_save( - output, - &gateway_args.proxy, - tx_args.outfile.as_deref(), - tx_args.wait_result, - ) - .await?; - } else { - save_output(&output, tx_args.outfile.as_deref())?; - } - Ok(()) -} - -/// Print the status and hex-encoded return values of a completed transaction. -pub(super) fn print_tx_results(tx_response: &multiversx_sc_scenario::scenario_model::TxResponse) { - if tx_response.tx_error.is_success() { - println!("Transaction successful."); - } else { - println!("Transaction failed: {}", tx_response.tx_error); - } - for (i, result) in tx_response.out.iter().enumerate() { - println!("Result[{i}]: 0x{}", hex::encode(result)); - } -} diff --git a/framework/meta/src/cmd/tx/tx_cli_send.rs b/framework/meta/src/cmd/tx/tx_cli_send.rs deleted file mode 100644 index 432b75de87..0000000000 --- a/framework/meta/src/cmd/tx/tx_cli_send.rs +++ /dev/null @@ -1,41 +0,0 @@ -use anyhow::Result; -use multiversx_sc_snippets::sdk::utils::base64_decode; - -use super::{ - output::TxOutputFile, - tx_cli_common::{broadcast_and_save, load_transaction_from_file}, -}; -use crate::cli::cli_args_tx::SendArgs; - -pub async fn tx_send(args: &SendArgs) { - if let Err(e) = tx_send_inner(args).await { - eprintln!("Error: {e:#}"); - std::process::exit(1); - } -} - -async fn tx_send_inner(args: &SendArgs) -> Result<()> { - let tx = load_transaction_from_file(&args.infile)?; - - let decoded_data = tx - .data - .as_ref() - .map(|d| String::from_utf8_lossy(&base64_decode(d)).into_owned()) - .unwrap_or_default(); - - let output = TxOutputFile { - emitted_transaction: tx, - emitted_transaction_data: decoded_data, - emitted_transaction_hash: String::new(), - contract_address: None, - transaction_on_network: None, - }; - - broadcast_and_save( - output, - &args.proxy, - args.outfile.as_deref(), - args.wait_result, - ) - .await -} diff --git a/framework/meta/src/cmd/tx/tx_cli_sign.rs b/framework/meta/src/cmd/tx/tx_cli_sign.rs deleted file mode 100644 index 02455d6bf4..0000000000 --- a/framework/meta/src/cmd/tx/tx_cli_sign.rs +++ /dev/null @@ -1,64 +0,0 @@ -use anyhow::{Result, anyhow}; -use multiversx_sc_snippets::{hex, sdk::utils::base64_decode}; - -use super::{ - output::TxOutputFile, - tx_cli_common::{broadcast_and_save, load_transaction_from_file, load_wallet, save_output}, -}; -use crate::cli::cli_args_tx::SignArgs; - -pub async fn tx_sign(args: &SignArgs) { - if let Err(e) = tx_sign_inner(args).await { - eprintln!("Error: {e:#}"); - std::process::exit(1); - } -} - -async fn tx_sign_inner(args: &SignArgs) -> Result<()> { - let wallet = load_wallet(&args.sender)?; - let mut tx = load_transaction_from_file(&args.infile)?; - - // Validate that the wallet address matches the transaction sender. - let wallet_address = wallet.to_address().to_bech32_default(); - if wallet_address != tx.sender { - return Err(anyhow!( - "wallet address {} does not match transaction sender {}", - wallet_address.to_bech32_str(), - tx.sender.to_bech32_str(), - )); - } - - if let Some(chain_id) = &args.gateway.chain { - tx.chain_id = chain_id.clone(); - } - - let sig = wallet.sign_tx(&tx); - tx.signature = Some(hex::encode(sig)); - - let decoded_data = tx - .data - .as_ref() - .map(|d| String::from_utf8_lossy(&base64_decode(d)).into_owned()) - .unwrap_or_default(); - - let output = TxOutputFile { - emitted_transaction: tx, - emitted_transaction_data: decoded_data, - emitted_transaction_hash: String::new(), - contract_address: None, - transaction_on_network: None, - }; - - if args.send { - broadcast_and_save( - output, - &args.gateway.proxy, - args.outfile.as_deref(), - args.wait_result, - ) - .await?; - } else { - save_output(&output, args.outfile.as_deref())?; - } - Ok(()) -} diff --git a/framework/meta/src/cmd/tx.rs b/framework/meta/src/cmd/tx_cli.rs similarity index 89% rename from framework/meta/src/cmd/tx.rs rename to framework/meta/src/cmd/tx_cli.rs index 90b264de26..66f4db35c0 100644 --- a/framework/meta/src/cmd/tx.rs +++ b/framework/meta/src/cmd/tx_cli.rs @@ -1,4 +1,3 @@ -mod output; mod parse_code_metadata; mod parse_payments; mod tx_cli_call; @@ -6,6 +5,7 @@ mod tx_cli_common; mod tx_cli_deploy; mod tx_cli_new; mod tx_cli_query; +mod tx_cli_relay; mod tx_cli_send; mod tx_cli_sign; mod tx_cli_upgrade; @@ -15,6 +15,7 @@ use tx_cli_call::tx_call; use tx_cli_deploy::tx_deploy; use tx_cli_new::tx_new; use tx_cli_query::tx_query; +use tx_cli_relay::tx_relay; use tx_cli_send::tx_send; use tx_cli_sign::tx_sign; use tx_cli_upgrade::tx_upgrade; @@ -28,5 +29,6 @@ pub async fn tx_cli(args: &TxCliArgs) { TxCliAction::New(new_args) => tx_new(new_args).await, TxCliAction::Send(send_args) => tx_send(send_args).await, TxCliAction::Sign(sign_args) => tx_sign(sign_args).await, + TxCliAction::Relay(relay_args) => tx_relay(relay_args).await, } } diff --git a/framework/meta/src/cmd/tx/TODO.md b/framework/meta/src/cmd/tx_cli/TODO.md similarity index 100% rename from framework/meta/src/cmd/tx/TODO.md rename to framework/meta/src/cmd/tx_cli/TODO.md diff --git a/framework/meta/src/cmd/tx/parse_code_metadata.rs b/framework/meta/src/cmd/tx_cli/parse_code_metadata.rs similarity index 98% rename from framework/meta/src/cmd/tx/parse_code_metadata.rs rename to framework/meta/src/cmd/tx_cli/parse_code_metadata.rs index 1b971bb0db..d9e2e9b530 100644 --- a/framework/meta/src/cmd/tx/parse_code_metadata.rs +++ b/framework/meta/src/cmd/tx_cli/parse_code_metadata.rs @@ -5,7 +5,7 @@ use crate::cli::cli_args_tx::MetadataArgs; /// Build a [`CodeMetadata`] bitfield from the CLI [`MetadataArgs`] flags. /// Defaults match standard contract conventions: upgradeable + readable, not payable. pub fn parse_code_metadata(meta: &MetadataArgs) -> CodeMetadata { - let mut flags = CodeMetadata::DEFAULT; + let mut flags = CodeMetadata::EMPTY; if !meta.metadata_not_upgradeable { flags |= CodeMetadata::UPGRADEABLE; } diff --git a/framework/meta/src/cmd/tx/parse_payments.rs b/framework/meta/src/cmd/tx_cli/parse_payments.rs similarity index 100% rename from framework/meta/src/cmd/tx/parse_payments.rs rename to framework/meta/src/cmd/tx_cli/parse_payments.rs diff --git a/framework/meta/src/cmd/tx/tx_cli_call.rs b/framework/meta/src/cmd/tx_cli/tx_cli_call.rs similarity index 53% rename from framework/meta/src/cmd/tx/tx_cli_call.rs rename to framework/meta/src/cmd/tx_cli/tx_cli_call.rs index 95c9a4f588..5d0e2044bb 100644 --- a/framework/meta/src/cmd/tx/tx_cli_call.rs +++ b/framework/meta/src/cmd/tx_cli/tx_cli_call.rs @@ -1,8 +1,10 @@ use anyhow::Result; -use multiversx_sc_snippets::imports::{Bech32Address, Interactor, InteractorIntoSdkTransaction}; +use multiversx_sc_snippets::imports::{Bech32Address, InteractorIntoSdkTransaction}; use super::parse_payments::parse_all_payment_args; -use super::tx_cli_common::{build_arg_buffer, load_wallet, sign_and_dispatch}; +use super::tx_cli_common::{ + build_arg_buffer, create_interactor, load_relayer_for_interactor, load_wallet, +}; use crate::cli::cli_args_tx::CallArgs; pub async fn tx_call(args: &CallArgs) { @@ -13,19 +15,13 @@ pub async fn tx_call(args: &CallArgs) { } async fn tx_call_inner(args: &CallArgs) -> Result<()> { - let wallet = load_wallet(&args.sender)?; + let sender_wallet = load_wallet(&args.sender)?; // Create the interactor – fetches network config. - let mut interactor = Interactor::new(&args.gateway.proxy).await; - let sender_address = interactor.register_wallet(wallet).await; + let mut interactor = create_interactor(&args.gateway, &args.tx).await?; + let sender_address = interactor.register_wallet(sender_wallet).await; let sender_bech32 = sender_address.to_bech32(interactor.get_hrp()); - - // Determine nonce. - let nonce = if let Some(n) = args.tx.nonce { - n - } else { - interactor.recall_nonce(&sender_address).await - }; + let relayer_address_opt = load_relayer_for_interactor(&mut interactor, &args.relayer).await?; let contract = Bech32Address::try_from_bech32_string(args.contract.clone())?; @@ -33,7 +29,7 @@ async fn tx_call_inner(args: &CallArgs) -> Result<()> { let arg_buffer = build_arg_buffer(&args.arguments)?; let payments = parse_all_payment_args(&args.payment)?; - let tx = interactor + let mut tx = interactor .tx() .from(&sender_bech32) .to(&contract) @@ -41,7 +37,16 @@ async fn tx_call_inner(args: &CallArgs) -> Result<()> { .payment(payments) .raw_call(args.function.as_str()) .arguments_raw(arg_buffer) + .opt_relayer(relayer_address_opt) .into_sdk_transaction(); - - sign_and_dispatch(wallet, tx, nonce, &args.tx, &args.gateway, None).await + interactor.set_tx_nonce_update_sender(&mut tx).await; + + interactor + .sign_and_dispatch( + tx, + args.tx.send, + args.tx.wait_result, + args.tx.outfile.as_deref(), + ) + .await } diff --git a/framework/meta/src/cmd/tx_cli/tx_cli_common.rs b/framework/meta/src/cmd/tx_cli/tx_cli_common.rs new file mode 100644 index 0000000000..d097822de6 --- /dev/null +++ b/framework/meta/src/cmd/tx_cli/tx_cli_common.rs @@ -0,0 +1,134 @@ +use std::fs; + +use anyhow::{Context, Result, anyhow}; +use multiversx_sc::imports::Bech32Address; +use multiversx_sc_snippets::{ + imports::{ + BytesValue, Interactor, InterpretableFrom, InterpreterContext, ManagedArgBuffer, + ManagedBuffer, StaticApi, + }, + sdk::data::transaction::Transaction, +}; + +use serde_json::Value; + +pub use crate::cli::cli_args_sender::{load_relayer_wallet, load_wallet}; +use crate::cli::cli_args_tx::{GatewayArgs, RelayerArgs, TxArgs}; + +/// Load a transaction from an mxpy-compatible interaction JSON file. +/// Accepts both `{"emittedTransaction": {...}}` and `{"tx": {...}}` wrappers. +pub(super) fn load_transaction_from_file(path: &std::path::Path) -> Result { + let content = + fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?; + let v: Value = serde_json::from_str(&content) + .with_context(|| format!("invalid JSON in {}", path.display()))?; + + let tx_value = v + .get("emittedTransaction") + .or_else(|| v.get("tx")) + .ok_or_else(|| { + anyhow!( + "file {} must contain an \"emittedTransaction\" or \"tx\" key", + path.display() + ) + })?; + + serde_json::from_value(tx_value.clone()) + .with_context(|| format!("failed to deserialize transaction from {}", path.display())) +} + +/// Interpret a list of mandos-format argument strings (e.g. `0x1a`, `str:hello`, `42`) +/// into a `ManagedArgBuffer` ready to be passed to `.arguments_raw()`. +pub fn build_arg_buffer(arguments: &[String]) -> Result> { + let context = + InterpreterContext::new().with_dir(std::env::current_dir().context("failed to get cwd")?); + let mut arg_buffer = ManagedArgBuffer::::new(); + for s in arguments { + let bv = BytesValue::interpret_from(s.as_str(), &context); + arg_buffer.push_arg_raw(ManagedBuffer::new_from_bytes(&bv.value)); + } + Ok(arg_buffer) +} + +/// Create an interactor connected to `gateway`, with chain-simulator mode auto-detected, +/// the explicit nonce override from `tx` applied, the gas price override applied, and +/// the chain ID validated against `gateway.chain` (if given). +pub(super) async fn create_interactor(gateway: &GatewayArgs, tx: &TxArgs) -> Result { + let mut interactor = Interactor::empty() + .with_connection(&gateway.proxy) + .await + .use_chain_simulator_auto(); + interactor.override_next_tx_nonce = tx.nonce; + apply_gas_price(&mut interactor, tx); + validate_chain_id(&interactor, gateway)?; + Ok(interactor) +} + +fn apply_gas_price(interactor: &mut Interactor, tx_args: &TxArgs) { + if let Some(gas_price) = tx_args.gas_price { + interactor.gas_price = gas_price; + } +} + +fn validate_chain_id(interactor: &Interactor, gateway_args: &GatewayArgs) -> Result<()> { + if let Some(chain_id) = &gateway_args.chain { + interactor.validate_chain_id(chain_id)?; + } + Ok(()) +} + +pub async fn load_relayer_for_interactor( + interactor: &mut Interactor, + relayer_args: &RelayerArgs, +) -> Result> { + let explicit_address = relayer_args + .relayer + .as_ref() + .map(|address| { + Bech32Address::try_from_bech32_string(address.clone()) + .map_err(|e| anyhow!("invalid --relayer address: {e}")) + }) + .transpose()?; + + let Some(wallet) = load_relayer_wallet(relayer_args)? else { + return Ok(explicit_address); + }; + + let wallet_address = interactor.register_wallet_bech32(wallet).await; + if let Some(explicit_address) = explicit_address { + if explicit_address != wallet_address { + return Err(anyhow!( + "relayer wallet address {} does not match --relayer address {}", + wallet_address.to_bech32_str(), + explicit_address.to_bech32_str(), + )); + } + Ok(Some(explicit_address)) + } else { + Ok(Some(wallet_address)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn explicit_relayer_address_does_not_register_wallet() { + let mut interactor = Interactor::empty(); + let relayer = Bech32Address::zero_default_hrp(); + let args = RelayerArgs { + relayer: Some(relayer.to_bech32_string()), + relayer_pem: None, + relayer_keyfile: None, + relayer_keystore_password: None, + }; + + let loaded_relayer = load_relayer_for_interactor(&mut interactor, &args) + .await + .unwrap(); + + assert_eq!(loaded_relayer, Some(relayer)); + assert!(interactor.sender_map.is_empty()); + } +} diff --git a/framework/meta/src/cmd/tx/tx_cli_deploy.rs b/framework/meta/src/cmd/tx_cli/tx_cli_deploy.rs similarity index 53% rename from framework/meta/src/cmd/tx/tx_cli_deploy.rs rename to framework/meta/src/cmd/tx_cli/tx_cli_deploy.rs index f6c09f3450..a7506c1c3d 100644 --- a/framework/meta/src/cmd/tx/tx_cli_deploy.rs +++ b/framework/meta/src/cmd/tx_cli/tx_cli_deploy.rs @@ -2,11 +2,12 @@ use std::fs; use anyhow::{Context, Result}; use multiversx_sc::chain_core::std::new_address::compute_new_address_bech32; -use multiversx_sc_snippets::ExplorerUrl; -use multiversx_sc_snippets::imports::{BytesValue, Interactor, InteractorIntoSdkTransaction}; +use multiversx_sc_snippets::imports::{BytesValue, InteractorIntoSdkTransaction}; use super::parse_code_metadata::parse_code_metadata; -use super::tx_cli_common::{build_arg_buffer, load_wallet, sign_and_dispatch}; +use super::tx_cli_common::{ + build_arg_buffer, create_interactor, load_relayer_for_interactor, load_wallet, +}; use crate::cli::cli_args_tx::DeployArgs; pub async fn tx_deploy(args: &DeployArgs) { @@ -17,19 +18,14 @@ pub async fn tx_deploy(args: &DeployArgs) { } async fn tx_deploy_inner(args: &DeployArgs) -> Result<()> { - let wallet = load_wallet(&args.sender)?; + let sender_wallet = load_wallet(&args.sender)?; // Create the interactor – this fetches the network config in the process. - let mut interactor = Interactor::new(&args.gateway.proxy).await; - let sender_address = interactor.register_wallet(wallet).await; - let sender_bech32 = sender_address.to_bech32(interactor.get_hrp()); - - // Determine nonce. - let nonce = if let Some(n) = args.tx.nonce { - n - } else { - interactor.recall_nonce(&sender_address).await - }; + let mut interactor = create_interactor(&args.gateway, &args.tx).await?; + let sender_address = interactor + .register_wallet_bech32(sender_wallet.clone()) + .await; + let relayer_address_opt = load_relayer_for_interactor(&mut interactor, &args.relayer).await?; // Read bytecode file and wrap in BytesValue so it implements TxCodeValue. let bytecode = fs::read(&args.bytecode) @@ -43,34 +39,32 @@ async fn tx_deploy_inner(args: &DeployArgs) -> Result<()> { let arg_buffer = build_arg_buffer(&args.arguments)?; let tx_builder = interactor .tx() - .from(&sender_bech32) + .from(&sender_address) .gas(args.tx.gas_limit) .egld(args.payment.value) .raw_deploy() .code(code) .code_metadata(code_metadata) - .arguments_raw(arg_buffer); + .arguments_raw(arg_buffer) + .opt_relayer(relayer_address_opt); - let tx = tx_builder.into_sdk_transaction(); + let mut tx = tx_builder.into_sdk_transaction(); + interactor.set_tx_nonce_update_sender(&mut tx).await; - let contract_address = compute_new_address_bech32(&tx.sender, nonce); + let contract_address = compute_new_address_bech32(&tx.sender, tx.nonce); - // TODO: refactor, by working with an interactor object in the future, - // who keeps the effective network config/chain ID - let effective_chain_id = args.gateway.chain.as_deref().unwrap_or(&tx.chain_id); - if let Some(ex) = ExplorerUrl::from_chain_id(effective_chain_id) { + if let Some(ex) = &interactor.explorer_url { println!("new contract: {}", ex.address_url(&contract_address)); } else { println!("new contract address: {contract_address}"); } - sign_and_dispatch( - wallet, - tx, - nonce, - &args.tx, - &args.gateway, - Some(contract_address.to_bech32_string()), - ) - .await + interactor + .sign_and_dispatch( + tx, + args.tx.send, + args.tx.wait_result, + args.tx.outfile.as_deref(), + ) + .await } diff --git a/framework/meta/src/cmd/tx/tx_cli_new.rs b/framework/meta/src/cmd/tx_cli/tx_cli_new.rs similarity index 50% rename from framework/meta/src/cmd/tx/tx_cli_new.rs rename to framework/meta/src/cmd/tx_cli/tx_cli_new.rs index ff6dce3f9e..9dd4ab4755 100644 --- a/framework/meta/src/cmd/tx/tx_cli_new.rs +++ b/framework/meta/src/cmd/tx_cli/tx_cli_new.rs @@ -1,21 +1,16 @@ use std::fs; use anyhow::{Context, Result}; -use multiversx_sc_snippets::{ - hex, - imports::{Bech32Address, Interactor, InteractorIntoSdkTransaction}, - sdk::utils::{base64_decode, base64_encode}, -}; +use multiversx_chain_core::std::base64_encode; +use multiversx_sc_snippets::imports::{Bech32Address, InteractorIntoSdkTransaction}; use crate::cli::cli_args_tx::NewArgs; -use crate::cmd::tx::tx_cli_common::load_wallet; - -use super::{ - output::TxOutputFile, - parse_payments::parse_all_payment_args, - tx_cli_common::{broadcast_and_save, save_output}, +use crate::cmd::tx_cli::tx_cli_common::{ + create_interactor, load_relayer_for_interactor, load_wallet, }; +use super::parse_payments::parse_all_payment_args; + pub async fn tx_new(args: &NewArgs) { if let Err(e) = tx_new_inner(args).await { eprintln!("Error: {e:#}"); @@ -24,29 +19,25 @@ pub async fn tx_new(args: &NewArgs) { } async fn tx_new_inner(args: &NewArgs) -> Result<()> { - let wallet = load_wallet(&args.sender)?; + let sender_wallet = load_wallet(&args.sender)?; let receiver = Bech32Address::try_from_bech32_string(args.receiver.clone())?; // Create the interactor – this fetches the network config in the process. - let mut interactor = Interactor::new(&args.gateway.proxy).await; - let sender_address = interactor.register_wallet(wallet).await; - let sender = sender_address.to_bech32(interactor.get_hrp()); - - // Determine nonce (explicit override or recalled from network). - let nonce = if let Some(n) = args.tx.nonce { - n - } else { - interactor.recall_nonce(&sender_address).await - }; + let mut interactor = create_interactor(&args.gateway, &args.tx).await?; + let sender_address = interactor + .register_wallet_bech32(sender_wallet.clone()) + .await; + let relayer_address_opt = load_relayer_for_interactor(&mut interactor, &args.relayer).await?; // Build Transaction via unified Tx syntax (resembles interactor code). let payments = parse_all_payment_args(&args.payment)?; let mut tx = interactor .tx() - .from(&sender) + .from(&sender_address) .to(&receiver) .gas(args.tx.gas_limit) .payment(payments) + .opt_relayer(relayer_address_opt) .into_sdk_transaction(); // Data field (mutually exclusive with --token-transfers; overrides only when provided). @@ -54,44 +45,16 @@ async fn tx_new_inner(args: &NewArgs) -> Result<()> { if !data_raw.is_empty() { tx.data = Some(base64_encode(&data_raw)); } + interactor.set_tx_nonce_update_sender(&mut tx).await; - // Decode the data field for the human-readable output. - let decoded_data = tx - .data - .as_ref() - .map(|d| String::from_utf8_lossy(&base64_decode(d)).into_owned()) - .unwrap_or_default(); - tx.nonce = nonce; - if let Some(gas_price) = args.tx.gas_price { - tx.gas_price = gas_price; - } - if let Some(chain_id) = &args.gateway.chain { - tx.chain_id = chain_id.clone(); - } - - let sig = wallet.sign_tx(&tx); - tx.signature = Some(hex::encode(sig)); - - let output = TxOutputFile { - emitted_transaction: tx, - emitted_transaction_data: decoded_data, - emitted_transaction_hash: String::new(), - contract_address: None, - transaction_on_network: None, - }; - - if args.tx.send { - broadcast_and_save( - output, - &args.gateway.proxy, - args.tx.outfile.as_deref(), + interactor + .sign_and_dispatch( + tx, + args.tx.send, args.tx.wait_result, + args.tx.outfile.as_deref(), ) - .await?; - } else { - save_output(&output, args.tx.outfile.as_deref())?; - } - Ok(()) + .await } // --------------------------------------------------------------------------- diff --git a/framework/meta/src/cmd/tx/tx_cli_query.rs b/framework/meta/src/cmd/tx_cli/tx_cli_query.rs similarity index 90% rename from framework/meta/src/cmd/tx/tx_cli_query.rs rename to framework/meta/src/cmd/tx_cli/tx_cli_query.rs index 84bba108cf..916e549dbf 100644 --- a/framework/meta/src/cmd/tx/tx_cli_query.rs +++ b/framework/meta/src/cmd/tx_cli/tx_cli_query.rs @@ -1,11 +1,11 @@ use anyhow::{Context, Result, anyhow}; use multiversx_sc_snippets::{ hex, - imports::{Bech32Address, GatewayHttpProxy}, + imports::{Bech32Address, GatewayHttpProxy, Interactor}, sdk::data::vm::VMQueryInput, }; -use super::tx_cli_common::{build_arg_buffer, to_json_pretty}; +use super::tx_cli_common::build_arg_buffer; use crate::cli::cli_args_tx::QueryArgs; pub async fn tx_query(args: &QueryArgs) { @@ -53,7 +53,7 @@ async fn tx_query_inner(args: &QueryArgs) -> Result<()> { .map(|bytes| hex::encode(&bytes)) .collect(); - let json = to_json_pretty(&return_data)?; + let json = Interactor::to_json_pretty(&return_data)?; println!("{json}"); Ok(()) } diff --git a/framework/meta/src/cmd/tx_cli/tx_cli_relay.rs b/framework/meta/src/cmd/tx_cli/tx_cli_relay.rs new file mode 100644 index 0000000000..2590d71b0f --- /dev/null +++ b/framework/meta/src/cmd/tx_cli/tx_cli_relay.rs @@ -0,0 +1,65 @@ +use anyhow::{Result, anyhow}; +use multiversx_sc_snippets::{Interactor, TxOutputFile}; + +use super::tx_cli_common::{load_relayer_wallet, load_transaction_from_file}; +use crate::cli::cli_args_tx::RelayArgs; + +pub async fn tx_relay(args: &RelayArgs) { + if let Err(e) = tx_relay_inner(args).await { + eprintln!("Error: {e:#}"); + std::process::exit(1); + } +} + +async fn tx_relay_inner(args: &RelayArgs) -> Result<()> { + let relayer_wallet = load_relayer_wallet(&args.relayer)?.ok_or_else(|| { + anyhow!("a relayer wallet is required: use --relayer-pem or --relayer-keyfile") + })?; + + let mut tx = load_transaction_from_file(&args.infile)?; + + // Validate that the transaction has a relayer field set. + let tx_relayer = tx + .relayer + .clone() + .ok_or_else(|| anyhow!("transaction does not have a relayer field; set it when building the transaction with --relayer "))?; + + // Validate that the sender has already signed. + if tx.signature.is_none() { + return Err(anyhow!( + "transaction is not signed by the sender; sign it first with `tx sign`" + )); + } + + // Validate that the relayer wallet matches the transaction's relayer field. + let relayer_addr = relayer_wallet.to_address().to_bech32_default(); + if relayer_addr != tx_relayer { + return Err(anyhow!( + "relayer wallet address {} does not match transaction relayer {}", + relayer_addr.to_bech32_str(), + tx_relayer.to_bech32_str(), + )); + } + + if let Some(chain_id) = &args.gateway.chain { + tx.chain_id = chain_id.clone(); + } + + let relayer_sig = relayer_wallet.sign_tx(&tx)?; + tx.relayer_signature = Some(relayer_sig); + + let output = TxOutputFile::from_transaction(tx)?; + + if args.send { + let interactor = Interactor::empty() + .with_connection(&args.gateway.proxy) + .await + .use_chain_simulator_auto(); + interactor + .broadcast_and_save(output, args.outfile.as_deref(), args.wait_result) + .await?; + } else { + output.save_output(args.outfile.as_deref())?; + } + Ok(()) +} diff --git a/framework/meta/src/cmd/tx_cli/tx_cli_send.rs b/framework/meta/src/cmd/tx_cli/tx_cli_send.rs new file mode 100644 index 0000000000..9c8c6ef082 --- /dev/null +++ b/framework/meta/src/cmd/tx_cli/tx_cli_send.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use multiversx_sc_snippets::{Interactor, TxOutputFile}; + +use super::tx_cli_common::load_transaction_from_file; +use crate::cli::cli_args_tx::SendArgs; + +pub async fn tx_send(args: &SendArgs) { + if let Err(e) = tx_send_inner(args).await { + eprintln!("Error: {e:#}"); + std::process::exit(1); + } +} + +async fn tx_send_inner(args: &SendArgs) -> Result<()> { + let tx = load_transaction_from_file(&args.infile)?; + + let output = TxOutputFile::from_transaction(tx)?; + let interactor = Interactor::empty() + .with_connection(&args.proxy) + .await + .use_chain_simulator_auto(); + interactor + .broadcast_and_save(output, args.outfile.as_deref(), args.wait_result) + .await +} diff --git a/framework/meta/src/cmd/tx_cli/tx_cli_sign.rs b/framework/meta/src/cmd/tx_cli/tx_cli_sign.rs new file mode 100644 index 0000000000..77569eb059 --- /dev/null +++ b/framework/meta/src/cmd/tx_cli/tx_cli_sign.rs @@ -0,0 +1,75 @@ +use anyhow::{Result, anyhow}; +use multiversx_chain_core::std::Bech32Address; +use multiversx_sc_snippets::{Interactor, TxOutputFile}; + +use super::tx_cli_common::{load_relayer_wallet, load_transaction_from_file, load_wallet}; +use crate::cli::cli_args_tx::SignArgs; + +pub async fn tx_sign(args: &SignArgs) { + if let Err(e) = tx_sign_inner(args).await { + eprintln!("Error: {e:#}"); + std::process::exit(1); + } +} + +async fn tx_sign_inner(args: &SignArgs) -> Result<()> { + let wallet = load_wallet(&args.sender)?; + let mut tx = load_transaction_from_file(&args.infile)?; + + // Validate that the wallet address matches the transaction sender. + let wallet_address = wallet.to_address().to_bech32_default(); + if wallet_address != tx.sender { + return Err(anyhow!( + "wallet address {} does not match transaction sender {}", + wallet_address.to_bech32_str(), + tx.sender.to_bech32_str(), + )); + } + + if let Some(chain_id) = &args.gateway.chain { + tx.chain_id = chain_id.clone(); + } + + if let Some(relayer_str) = &args.relayer.relayer { + let relayer_addr = Bech32Address::try_from_bech32_string(relayer_str.clone()) + .map_err(|e| anyhow!("invalid --relayer address: {e}"))?; + tx.relayer = Some(relayer_addr); + } + + let sig = wallet.sign_tx(&tx)?; + tx.signature = Some(sig); + + // Optionally sign as relayer. + if let Some(relayer_w) = load_relayer_wallet(&args.relayer)? { + let relayer_addr = relayer_w.to_address().to_bech32_default(); + if let Some(tx_relayer) = &tx.relayer { + if relayer_addr != *tx_relayer { + return Err(anyhow!( + "relayer wallet address {} does not match transaction relayer {}", + relayer_addr.to_bech32_str(), + tx_relayer.to_bech32_str(), + )); + } + } else { + // No relayer field in the tx yet — derive it from the wallet. + tx.relayer = Some(relayer_addr); + } + let relayer_sig = relayer_w.sign_tx(&tx)?; + tx.relayer_signature = Some(relayer_sig); + } + + let output = TxOutputFile::from_transaction(tx)?; + + if args.send { + let interactor = Interactor::empty() + .with_connection(&args.gateway.proxy) + .await + .use_chain_simulator_auto(); + interactor + .broadcast_and_save(output, args.outfile.as_deref(), args.wait_result) + .await?; + } else { + output.save_output(args.outfile.as_deref())?; + } + Ok(()) +} diff --git a/framework/meta/src/cmd/tx/tx_cli_upgrade.rs b/framework/meta/src/cmd/tx_cli/tx_cli_upgrade.rs similarity index 64% rename from framework/meta/src/cmd/tx/tx_cli_upgrade.rs rename to framework/meta/src/cmd/tx_cli/tx_cli_upgrade.rs index 6f3714fe4b..25ccc41949 100644 --- a/framework/meta/src/cmd/tx/tx_cli_upgrade.rs +++ b/framework/meta/src/cmd/tx_cli/tx_cli_upgrade.rs @@ -2,12 +2,12 @@ use std::fs; use anyhow::{Context, Result}; use multiversx_sc_scenario::imports::NotPayable; -use multiversx_sc_snippets::imports::{ - Bech32Address, BytesValue, Interactor, InteractorIntoSdkTransaction, -}; +use multiversx_sc_snippets::imports::{Bech32Address, BytesValue, InteractorIntoSdkTransaction}; use super::parse_code_metadata::parse_code_metadata; -use super::tx_cli_common::{build_arg_buffer, load_wallet, sign_and_dispatch}; +use super::tx_cli_common::{ + build_arg_buffer, create_interactor, load_relayer_for_interactor, load_wallet, +}; use crate::cli::cli_args_tx::UpgradeArgs; pub async fn tx_upgrade(args: &UpgradeArgs) { @@ -21,16 +21,10 @@ async fn tx_upgrade_inner(args: &UpgradeArgs) -> Result<()> { let wallet = load_wallet(&args.sender)?; // Create the interactor – this fetches the network config. - let mut interactor = Interactor::new(&args.gateway.proxy).await; + let mut interactor = create_interactor(&args.gateway, &args.tx).await?; let sender_address = interactor.register_wallet(wallet).await; let sender_bech32 = sender_address.to_bech32(interactor.get_hrp()); - - // Determine nonce. - let nonce = if let Some(n) = args.tx.nonce { - n - } else { - interactor.recall_nonce(&sender_address).await - }; + let relayer_address_opt = load_relayer_for_interactor(&mut interactor, &args.relayer).await?; let contract = Bech32Address::try_from_bech32_string(args.contract.clone())?; @@ -43,7 +37,7 @@ async fn tx_upgrade_inner(args: &UpgradeArgs) -> Result<()> { // Build upgrade transaction — same layout as deploy but sent to the existing contract address. let arg_buffer = build_arg_buffer(&args.arguments)?; - let tx_builder = interactor + let mut tx = interactor .tx() .from(&sender_bech32) .to(&contract) @@ -52,9 +46,17 @@ async fn tx_upgrade_inner(args: &UpgradeArgs) -> Result<()> { .raw_upgrade() .code(code) .code_metadata(code_metadata) - .arguments_raw(arg_buffer); - - let tx = tx_builder.into_sdk_transaction(); - - sign_and_dispatch(wallet, tx, nonce, &args.tx, &args.gateway, None).await + .arguments_raw(arg_buffer) + .opt_relayer(relayer_address_opt) + .into_sdk_transaction(); + interactor.set_tx_nonce_update_sender(&mut tx).await; + + interactor + .sign_and_dispatch( + tx, + args.tx.send, + args.tx.wait_result, + args.tx.outfile.as_deref(), + ) + .await } diff --git a/framework/meta/src/cmd/wallet.rs b/framework/meta/src/cmd/wallet_cmd.rs similarity index 55% rename from framework/meta/src/cmd/wallet.rs rename to framework/meta/src/cmd/wallet_cmd.rs index 60125cb6c9..08eef88e63 100644 --- a/framework/meta/src/cmd/wallet.rs +++ b/framework/meta/src/cmd/wallet_cmd.rs @@ -1,17 +1,27 @@ -use core::str; +use std::{ + fs::{self, File}, + io::{self, Read, Write}, +}; + +use bip39::Language; +use rand::Rng; + +use multiversx_sc::types::Address; +use multiversx_sc_snippets::{ + hex, + imports::Bech32Address, + sdk::{ + chain_core::std::Bech32Hrp, + wallet::{Keystore, KeystoreRandomness, Wallet}, + }, +}; +use multiversx_sdk::wallet::{KeystoreError, Mnemonic, WalletPem}; +use crate::cli::cli_args_sender::get_keystore_password; use crate::cli::{ WalletAction, WalletArgs, WalletBech32Args, WalletConvertArgs, WalletNewArgs, WalletTestWalletArgs, }; -use bip39::{Language, Mnemonic}; -use multiversx_sc::types::{self, Address}; -use multiversx_sc_snippets::sdk::{crypto::public_key::PublicKey, wallet::Wallet}; -use multiversx_sc_snippets::{hex, imports::Bech32Address}; -use std::{ - fs::{self, File}, - io::{self, Read, Write}, -}; pub fn wallet(args: &WalletArgs) { let command = &args.command; @@ -30,40 +40,43 @@ fn convert(convert_args: &WalletConvertArgs) { let out_format = &convert_args.to; let hrp = convert_args .hrp - .clone() - .unwrap_or_else(|| "erd".to_string()); + .as_deref() + .map(|hrp| Bech32Hrp::try_from(hrp).expect("invalid HRP")) + .unwrap_or_default(); let mut mnemonic_str = String::new(); - let private_key_str: String; - let public_key_str: String; match (in_format.as_str(), out_format.as_str()) { ("mnemonic", "pem") => match infile { Some(file) => { mnemonic_str = fs::read_to_string(file).unwrap(); - (private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic_str); - write_resulted_pem(&hrp, &public_key_str, &private_key_str, outfile); + let wallet = Wallet::try_from(Mnemonic::parse(&mnemonic_str).unwrap()).unwrap(); + write_resulted_pem(wallet.to_pem(hrp).unwrap(), outfile); } None => { println!( "Insert text below. Press 'Ctrl-D' (Linux / MacOS) or 'Ctrl-Z' (Windows) when done." ); _ = io::stdin().read_to_string(&mut mnemonic_str).unwrap(); - (private_key_str, public_key_str) = Wallet::get_wallet_keys_mnemonic(mnemonic_str); - write_resulted_pem(&hrp, &public_key_str, &private_key_str, outfile); + let wallet = Wallet::try_from(Mnemonic::parse(&mnemonic_str).unwrap()).unwrap(); + write_resulted_pem(wallet.to_pem(hrp).unwrap(), outfile); } }, ("keystore-secret", "pem") => match infile { Some(file) => { - let private_key = Wallet::get_private_key_from_keystore_secret( - file, - &Wallet::get_keystore_password(), - ) - .unwrap(); - private_key_str = private_key.to_string(); - let public_key = PublicKey::from(&private_key); - public_key_str = public_key.to_string(); - write_resulted_pem(&hrp, &public_key_str, &private_key_str, outfile); + let keystore = Keystore::from_file(file).expect("keystore parsing error"); + match keystore.decrypt_wallet(&get_keystore_password()) { + Ok(wallet) => { + println!("Password is correct"); + write_resulted_pem(wallet.to_pem(hrp).unwrap(), outfile); + } + Err(KeystoreError::InvalidPassword) => { + panic!("Password is incorrect"); + } + Err(e) => { + panic!("Failed to extract private key from keystore: {e}"); + } + } } None => { panic!("Input file is required for keystore-secret format"); @@ -71,19 +84,15 @@ fn convert(convert_args: &WalletConvertArgs) { }, ("pem", "keystore-secret") => match infile { Some(file) => { - let pem_decoded_keys = Wallet::get_pem_decoded_content(file); - (private_key_str, public_key_str) = Wallet::get_wallet_keys_pem(file); - - let address = get_wallet_address(&private_key_str); - let hex_decoded_keys = hex::decode(pem_decoded_keys).unwrap(); - - let json_result = Wallet::encrypt_keystore( - hex_decoded_keys.as_slice(), - &hrp, - &address, - &public_key_str, - &Wallet::get_keystore_password(), - ); + let wallet_pem = WalletPem::from_pem_file(file).expect("error reading PEM file"); + let randomness = new_keystore_randomness(); + let json_result = Keystore::encrypt( + &wallet_pem.private_key, + hrp, + &get_keystore_password(), + randomness, + ) + .to_json_string(); write_resulted_keystore(json_result, outfile); } None => { @@ -96,16 +105,14 @@ fn convert(convert_args: &WalletConvertArgs) { } } -fn write_resulted_pem(hrp: &str, public_key: &str, private_key: &str, outfile: Option<&String>) { - let address = get_wallet_address(private_key); +fn write_resulted_pem(wallet_pem: WalletPem, outfile: Option<&String>) { + let pem_content = wallet_pem.to_pem_str(); match outfile { Some(outfile) => { - let pem_content = Wallet::generate_pem_content(hrp, &address, private_key, public_key); let mut file = File::create(outfile).unwrap(); file.write_all(pem_content.as_bytes()).unwrap(); } None => { - let pem_content = Wallet::generate_pem_content(hrp, &address, private_key, public_key); print!("{}", pem_content); } } @@ -123,6 +130,18 @@ fn write_resulted_keystore(json_result: String, outfile: Option<&String>) { } } +pub fn new_keystore_randomness() -> KeystoreRandomness { + let mut salt = [0u8; 32]; + let mut iv = [0u8; 16]; + rand::rng().fill_bytes(&mut salt); + rand::rng().fill_bytes(&mut iv); + KeystoreRandomness { + salt, + iv, + id: uuid::Uuid::new_v4().to_string(), + } +} + fn bech32_conversion(bech32_args: &WalletBech32Args) { let encode_address = bech32_args.hex_address.as_ref(); let decode_address = bech32_args.bech32_address.as_ref(); @@ -132,7 +151,7 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) { let bytes_from_hex = hex::decode(hex).unwrap(); let bytes_arr: [u8; 32] = bytes_from_hex.try_into().unwrap(); - let addr = types::Address::from(&bytes_arr); + let addr = Address::from(&bytes_arr); let bech32_addr = Bech32Address::from(addr).to_bech32_str().to_string(); println!("{}", bech32_addr); } @@ -148,42 +167,28 @@ fn bech32_conversion(bech32_args: &WalletBech32Args) { } } -fn get_wallet_address(private_key: &str) -> Address { - let wallet = Wallet::from_private_key(private_key).unwrap(); - wallet.to_address() -} - pub fn generate_mnemonic() -> Mnemonic { - Mnemonic::generate_in(Language::English, 24).unwrap() + Mnemonic::from(bip39::Mnemonic::generate_in(Language::English, 24).unwrap()) } -struct WalletInfo { +struct NewWalletInfo { mnemonic: Mnemonic, - private_key_str: String, - public_key_str: String, - address: Address, + wallet: Wallet, } -impl WalletInfo { +impl NewWalletInfo { fn generate() -> Self { let mnemonic = generate_mnemonic(); - let (private_key_str, public_key_str) = - Wallet::get_wallet_keys_mnemonic(mnemonic.to_string()); - let address = get_wallet_address(private_key_str.as_str()); - WalletInfo { - mnemonic, - private_key_str, - public_key_str, - address, - } + let wallet = Wallet::try_from(mnemonic.clone()).unwrap(); + NewWalletInfo { mnemonic, wallet } } fn generate_for_shard(shard: u8) -> Self { assert!(shard < 3, "Shard must be between 0 and 2"); loop { - let wallet = Self::generate(); - if wallet.address.shard_of_3().as_u32() == shard as u32 { - return wallet; + let wallet_info = Self::generate(); + if wallet_info.wallet.address.shard_of_3().as_u32() == shard as u32 { + return wallet_info; } } } @@ -192,56 +197,52 @@ impl WalletInfo { fn new(new_args: &WalletNewArgs) { let format = new_args.wallet_format.as_deref(); let outfile = new_args.outfile.as_ref(); - let hrp = new_args.hrp.clone().unwrap_or_else(|| "erd".to_string()); + let hrp = new_args + .hrp + .as_deref() + .map(|hrp| Bech32Hrp::try_from(hrp).expect("invalid HRP")) + .unwrap_or_default(); - let wallet = if let Some(shard) = new_args.shard { - WalletInfo::generate_for_shard(shard) + let new_wallet_info = if let Some(shard) = new_args.shard { + NewWalletInfo::generate_for_shard(shard) } else { - WalletInfo::generate() + NewWalletInfo::generate() }; - let WalletInfo { - mnemonic, - private_key_str, - public_key_str, - address, - } = wallet; - - println!("Mnemonic: {}", mnemonic); + println!("Mnemonic: {}", new_wallet_info.mnemonic); println!("Wallet address:"); - println!(" - bech32: {}", address.to_bech32(&hrp)); - println!(" - hex: 0x{}", address.to_hex()); + println!( + " - bech32: {}", + new_wallet_info.wallet.address.to_bech32(hrp) + ); + println!(" - hex: 0x{}", new_wallet_info.wallet.address.to_hex()); match format { Some("pem") => { - write_resulted_pem( - &hrp, - public_key_str.as_str(), - private_key_str.as_str(), - outfile, - ); + write_resulted_pem(new_wallet_info.wallet.to_pem(hrp).unwrap(), outfile); if let Some(outfile) = outfile { println!("Wallet saved to '{outfile}'"); } } Some("keystore-secret") => { - let concatenated_keys = format!("{}{}", private_key_str, public_key_str); - let hex_decoded_keys = hex::decode(concatenated_keys).unwrap(); - let json_result = Wallet::encrypt_keystore( - hex_decoded_keys.as_slice(), - &hrp, - &address, - &public_key_str, - &Wallet::get_keystore_password(), - ); + let randomness = new_keystore_randomness(); + let json_result = Keystore::encrypt( + new_wallet_info.wallet.private_key().unwrap(), + hrp, + &get_keystore_password(), + randomness, + ) + .to_json_string(); write_resulted_keystore(json_result, outfile); if let Some(outfile) = outfile { println!("Wallet saved to '{outfile}'"); } } - Some(_) => { - println!("Unsupported format"); + Some(format) => { + println!( + "Unsupported format: '{format}'. Supported formats are 'pem' and 'keystore-secret'." + ); } None => {} } diff --git a/framework/meta/src/folder_structure/strip_path.rs b/framework/meta/src/folder_structure/strip_path.rs index f1ef6284e3..3043f2f919 100644 --- a/framework/meta/src/folder_structure/strip_path.rs +++ b/framework/meta/src/folder_structure/strip_path.rs @@ -1,17 +1,18 @@ use super::{DirectoryType, FRAMEWORK_CRATE_NAMES, RelevantDirectories, RelevantDirectory}; -use multiversx_sc_meta_lib::cargo_toml::CargoTomlContents; +use multiversx_sc_meta_lib::cargo_toml::{CargoTomlContents, WorkspaceDependencies}; use std::path::Path; /// Recursively finds all crates under `path` that depend on a framework crate, -/// then removes `path = "..."` from those framework dependencies in each +/// resolves any `workspace = true` entries using `workspace_dependencies`, then +/// removes `path = "..."` from those framework dependencies in each /// `Cargo.toml`. Directories named in `ignore` are skipped during traversal. /// For contract crates, the `meta/` subdirectory is also processed. -pub fn strip_path(path: &Path, ignore: &[String]) { +pub fn strip_path(path: &Path, ignore: &[String], workspace_dependencies: &WorkspaceDependencies) { let dirs = RelevantDirectories::find_all(path, ignore); let mut count = 0; for dir in dirs.iter() { - if strip_framework_paths(dir) { + if strip_framework_paths(dir, workspace_dependencies) { count += 1; } // `populate_directories` stops recursing into a contract crate's children, @@ -21,7 +22,7 @@ pub fn strip_path(path: &Path, ignore: &[String]) { path: dir.meta_path(), ..dir.clone() }; - if strip_framework_paths(&meta_dir) { + if strip_framework_paths(&meta_dir, workspace_dependencies) { count += 1; } } @@ -30,21 +31,28 @@ pub fn strip_path(path: &Path, ignore: &[String]) { println!("Done. Stripped path from {count} Cargo.toml file(s)."); } -/// Removes `path = "..."` from framework dependencies in a directory's Cargo.toml. +/// Resolves `workspace = true` entries, then removes `path = "..."` from +/// framework dependencies in a directory's Cargo.toml. /// Returns `true` if the file was modified. -fn strip_framework_paths(dir: &RelevantDirectory) -> bool { +fn strip_framework_paths( + dir: &RelevantDirectory, + workspace_dependencies: &WorkspaceDependencies, +) -> bool { let cargo_toml_path = dir.path.join("Cargo.toml"); if !cargo_toml_path.is_file() { return false; } let mut cargo_toml = CargoTomlContents::load_from_file(&cargo_toml_path); - let changed = cargo_toml.strip_dependency_paths(FRAMEWORK_CRATE_NAMES); + let ws_changed = cargo_toml.resolve_workspace_dependencies(workspace_dependencies); + let paths_stripped = cargo_toml.strip_dependency_paths(FRAMEWORK_CRATE_NAMES); - if changed { + if paths_stripped { println!("Stripped: {}", cargo_toml_path.display()); + } + if ws_changed || paths_stripped { cargo_toml.save_to_file(&cargo_toml_path); } - changed + paths_stripped } diff --git a/framework/meta/src/lib.rs b/framework/meta/src/lib.rs index dd07012de9..c5ddb7a557 100644 --- a/framework/meta/src/lib.rs +++ b/framework/meta/src/lib.rs @@ -8,6 +8,7 @@ pub mod cmd; pub mod folder_structure; pub use multiversx_sc_meta_lib::abi_json; +pub use multiversx_sc_meta_lib::cargo_toml; pub use multiversx_sc_meta_lib::ei; pub use multiversx_sc_meta_lib::ei_check_json; pub use multiversx_sc_meta_lib::version; diff --git a/framework/meta/tests/cs_tx_cli_test.rs b/framework/meta/tests/cs_tx_cli_test.rs index 17f07ef97f..9294d937ea 100644 --- a/framework/meta/tests/cs_tx_cli_test.rs +++ b/framework/meta/tests/cs_tx_cli_test.rs @@ -11,9 +11,20 @@ const CHAIN_SIMULATOR_CHAIN_ID: &str = "chain"; /// 0.1 EGLD in the smallest denomination (10^17). const TRANSFER_AMOUNT: u128 = 100_000_000_000_000_000; +/// 100 EGLD, the amount senders get funded automatically +/// on interactor/sc-meta CLI + chain simulator. +const FUND_AMOUNT: u128 = 100_000_000_000_000_000_000; + /// Minimum gas for a plain EGLD transfer. const GAS_LIMIT: u64 = 50_000; +struct AdderTestContext<'a> { + sc_meta_bin: &'a str, + wasm_path: &'a std::path::Path, + wallet_pem_path: &'a std::path::Path, + wallet_address: &'a str, +} + /// Deploys the adder contract, calls `add`, and verifies `getSum` returns the expected value. /// Mirrors the deploy / add / getSum flow from the adder snippets.sh. #[tokio::test] @@ -32,25 +43,55 @@ async fn test_adder_deploy_add_get_sum() { let sc_meta_bin = env!("CARGO_BIN_EXE_sc-meta"); + let wallet = Wallet::from_pem_file(&wallet_pem_path).unwrap(); + let wallet_address = wallet + .to_address() + .to_bech32_default() + .to_bech32_str() + .to_owned(); + let context = AdderTestContext { + sc_meta_bin, + wasm_path: &wasm_path, + wallet_pem_path: &wallet_pem_path, + wallet_address: &wallet_address, + }; + + // Connect to the chain simulator, generate some initial blocks. let interactor = Interactor::new(CHAIN_SIMULATOR_URL) .await .use_chain_simulator(true); + interactor.generate_blocks(10).await.unwrap(); - let wallet = Wallet::from_pem_file(&wallet_pem_path).unwrap(); - let wallet_address = wallet.to_address().to_bech32_default(); - interactor.send_user_funds(&wallet_address).await.unwrap(); + let contract_address = deploy_adder(&context, &outfile_deploy); + call_adder(&context, &contract_address, &outfile_call, 5); + assert_eq!(query_sum(&context, &contract_address), vec!["05"]); - interactor.generate_blocks(10).await.unwrap(); + upgrade_adder(&context, &contract_address, &outfile_upgrade); + assert_eq!(query_sum(&context, &contract_address), vec!["0a"]); + + let relayer_pem_path = test_artefacts_dir.join("s1mon.pem"); + let outfile_call_relayed = outfiles_dir.join("adder-call-relayed-cs.interaction.json"); + + generate_test_wallet(context.sc_meta_bin, "s1mon", &relayer_pem_path); + call_adder_relayed( + &context, + &contract_address, + &relayer_pem_path, + &outfile_call_relayed, + "3", + ); + assert_eq!(query_sum(&context, &contract_address), vec!["0d"]); +} - // ── deploy ──────────────────────────────────────────────────────────────── - let deploy_output = Command::new(sc_meta_bin) +fn deploy_adder(context: &AdderTestContext<'_>, outfile: &std::path::Path) -> String { + let output = Command::new(context.sc_meta_bin) .args([ "tx", "deploy", "--bytecode", - wasm_path.to_str().unwrap(), + context.wasm_path.to_str().unwrap(), "--pem", - wallet_pem_path.to_str().unwrap(), + context.wallet_pem_path.to_str().unwrap(), "--proxy", CHAIN_SIMULATOR_URL, "--chain", @@ -60,67 +101,64 @@ async fn test_adder_deploy_add_get_sum() { "--arguments", "0", "--send", + "--wait-result", "--outfile", - outfile_deploy.to_str().unwrap(), + outfile.to_str().unwrap(), ]) .output() .expect("failed to execute sc-meta tx deploy"); - println!( "deploy stdout:\n{}", - String::from_utf8_lossy(&deploy_output.stdout) + String::from_utf8_lossy(&output.stdout) ); - println!( - "deploy stderr:\n{}", - String::from_utf8_lossy(&deploy_output.stderr) + assert!( + output.status.success(), + "deploy failed:\n{}", + String::from_utf8_lossy(&output.stderr) ); - assert!(deploy_output.status.success(), "deploy failed"); - - interactor.generate_blocks(10).await.unwrap(); - - // Read the deployed contract address from the interaction output file. - let outfile_content = - std::fs::read_to_string(&outfile_deploy).expect("failed to read deploy outfile"); - let deploy_json: serde_json::Value = - serde_json::from_str(&outfile_content).expect("failed to parse deploy outfile JSON"); - // Verify deterministic deploy fields. + let content = std::fs::read_to_string(outfile).expect("failed to read deploy outfile"); + let json: serde_json::Value = + serde_json::from_str(&content).expect("failed to parse deploy outfile JSON"); assert_eq!( - deploy_json["emittedTransaction"]["sender"] - .as_str() - .unwrap(), - wallet_address.to_bech32_str(), + json["emittedTransaction"]["sender"].as_str().unwrap(), + context.wallet_address, "deploy sender mismatch" ); assert_eq!( - deploy_json["emittedTransaction"]["receiver"] - .as_str() - .unwrap(), + json["emittedTransaction"]["receiver"].as_str().unwrap(), Bech32Address::zero_default_hrp().to_bech32_str(), "deploy receiver mismatch" ); assert!( - deploy_json["emittedTransactionData"] + json["emittedTransactionData"] .as_str() .unwrap() .ends_with("@0500@0500@"), "deploy emittedTransactionData does not end with @0500@0500@" ); - let contract_address = deploy_json["contractAddress"] + let contract_address = json["contractAddress"] .as_str() - .expect("contractAddress not found in deploy outfile"); - + .expect("contractAddress not found in deploy outfile") + .to_owned(); println!("Deployed adder at: {contract_address}"); + contract_address +} - // ── add(5) ──────────────────────────────────────────────────────────────── - let status = Command::new(sc_meta_bin) +fn call_adder( + context: &AdderTestContext<'_>, + contract_address: &str, + outfile: &std::path::Path, + argument: usize, +) { + let output = Command::new(context.sc_meta_bin) .args([ "tx", "call", contract_address, "--pem", - wallet_pem_path.to_str().unwrap(), + context.wallet_pem_path.to_str().unwrap(), "--proxy", CHAIN_SIMULATOR_URL, "--chain", @@ -130,43 +168,40 @@ async fn test_adder_deploy_add_get_sum() { "--function", "add", "--arguments", - "5", + argument.to_string().as_str(), "--send", + "--wait-result", "--outfile", - outfile_call.to_str().unwrap(), + outfile.to_str().unwrap(), ]) - .status() + .output() .expect("failed to execute sc-meta tx call"); + println!("add stdout:\n{}", String::from_utf8_lossy(&output.stdout)); + assert!(output.status.success(), "add call failed"); - assert!(status.success(), "add call failed"); - - interactor.generate_blocks(10).await.unwrap(); - - // Read and verify deterministic call fields. - let call_content = std::fs::read_to_string(&outfile_call).expect("failed to read call outfile"); - let call_json: serde_json::Value = - serde_json::from_str(&call_content).expect("failed to parse call outfile JSON"); - + let content = std::fs::read_to_string(outfile).expect("failed to read call outfile"); + let json: serde_json::Value = + serde_json::from_str(&content).expect("failed to parse call outfile JSON"); assert_eq!( - call_json["emittedTransaction"]["sender"].as_str().unwrap(), - wallet_address.to_bech32_str(), + json["emittedTransaction"]["sender"].as_str().unwrap(), + context.wallet_address, "call sender mismatch" ); assert_eq!( - call_json["emittedTransaction"]["receiver"] - .as_str() - .unwrap(), + json["emittedTransaction"]["receiver"].as_str().unwrap(), contract_address, "call receiver mismatch" ); + let expected_transaction_data = format!("add@{:02x}", argument); assert_eq!( - call_json["emittedTransactionData"].as_str().unwrap(), - "add@05", + json["emittedTransactionData"].as_str().unwrap(), + expected_transaction_data, "call emittedTransactionData mismatch" ); +} - // ── getSum ──────────────────────────────────────────────────────────────── - let query_output = Command::new(sc_meta_bin) +fn query_sum(context: &AdderTestContext<'_>, contract_address: &str) -> Vec { + let output = Command::new(context.sc_meta_bin) .args([ "tx", "query", @@ -178,94 +213,96 @@ async fn test_adder_deploy_add_get_sum() { ]) .output() .expect("failed to execute sc-meta tx query"); + assert!(output.status.success(), "getSum query failed"); - assert!(query_output.status.success(), "getSum query failed"); - - let stdout = String::from_utf8_lossy(&query_output.stdout); + let stdout = String::from_utf8_lossy(&output.stdout); println!("getSum result: {stdout}"); + serde_json::from_str(stdout.trim()).expect("failed to parse query output as JSON") +} - // The result is a JSON array of hex-encoded values, e.g. ["05"]. - // 5 decimal = 0x05. - let result: Vec = - serde_json::from_str(stdout.trim()).expect("failed to parse query output as JSON"); - assert_eq!(result, vec!["05"], "getSum returned unexpected value"); - - // ── upgrade ─────────────────────────────────────────────────────────────── - let upgrade_output = Command::new(sc_meta_bin) +fn upgrade_adder( + context: &AdderTestContext<'_>, + contract_address: &str, + outfile: &std::path::Path, +) { + let output = Command::new(context.sc_meta_bin) .args([ "tx", "upgrade", contract_address, "--bytecode", - wasm_path.to_str().unwrap(), + context.wasm_path.to_str().unwrap(), "--pem", - wallet_pem_path.to_str().unwrap(), + context.wallet_pem_path.to_str().unwrap(), "--proxy", CHAIN_SIMULATOR_URL, "--chain", CHAIN_SIMULATOR_CHAIN_ID, "--gas-limit", "50000000", + "--arguments", + "10", "--send", + "--wait-result", "--outfile", - outfile_upgrade.to_str().unwrap(), + outfile.to_str().unwrap(), ]) .output() .expect("failed to execute sc-meta tx upgrade"); - println!( "upgrade stdout:\n{}", - String::from_utf8_lossy(&upgrade_output.stdout) + String::from_utf8_lossy(&output.stdout) ); - println!( - "upgrade stderr:\n{}", - String::from_utf8_lossy(&upgrade_output.stderr) - ); - assert!(upgrade_output.status.success(), "upgrade failed"); - - interactor.generate_blocks(10).await.unwrap(); - - // Verify the upgrade outfile references the same contract address. - let upgrade_content = - std::fs::read_to_string(&outfile_upgrade).expect("failed to read upgrade outfile"); - let upgrade_json: serde_json::Value = - serde_json::from_str(&upgrade_content).expect("failed to parse upgrade outfile JSON"); + assert!(output.status.success(), "upgrade failed"); + let content = std::fs::read_to_string(outfile).expect("failed to read upgrade outfile"); + let json: serde_json::Value = + serde_json::from_str(&content).expect("failed to parse upgrade outfile JSON"); assert_eq!( - upgrade_json["emittedTransaction"]["receiver"] - .as_str() - .unwrap(), + json["emittedTransaction"]["receiver"].as_str().unwrap(), contract_address, "upgrade receiver mismatch" ); +} - // getSum must still return 5 after the upgrade. - let query_after_upgrade = Command::new(sc_meta_bin) +fn call_adder_relayed( + context: &AdderTestContext<'_>, + contract_address: &str, + relayer_pem_path: &std::path::Path, + outfile: &std::path::Path, + argument: &str, +) { + let output = Command::new(context.sc_meta_bin) .args([ "tx", - "query", + "call", contract_address, + "--pem", + context.wallet_pem_path.to_str().unwrap(), + "--relayer-pem", + relayer_pem_path.to_str().unwrap(), "--proxy", CHAIN_SIMULATOR_URL, + "--chain", + CHAIN_SIMULATOR_CHAIN_ID, + "--gas-limit", + "5050000", "--function", - "getSum", + "add", + "--arguments", + argument, + "--send", + "--wait-result", + "--outfile", + outfile.to_str().unwrap(), ]) .output() - .expect("failed to execute sc-meta tx query after upgrade"); - - assert!( - query_after_upgrade.status.success(), - "getSum query after upgrade failed" - ); - - let stdout_after = String::from_utf8_lossy(&query_after_upgrade.stdout); - let result_after: Vec = - serde_json::from_str(stdout_after.trim()).expect("failed to parse query output as JSON"); - assert_eq!( - result_after, - vec!["05"], - "getSum returned unexpected value after upgrade" + .expect("failed to execute sc-meta tx call (relayed)"); + println!( + "relayed add stdout:\n{}", + String::from_utf8_lossy(&output.stdout) ); + assert!(output.status.success(), "relayed add call failed"); } /// Sends a small amount of EGLD from Alice to Bob via the `sc-meta tx new` CLI command @@ -281,38 +318,19 @@ async fn test_egld_transfer_alice_to_bob() { let sc_meta_bin = env!("CARGO_BIN_EXE_sc-meta"); // Generate alice.pem via the CLI so the file is not stored in the repo. - let status = Command::new(sc_meta_bin) - .args([ - "wallet", - "test-wallet", - "--name", - "alice", - "--path", - alice_pem_path.to_str().unwrap(), - ]) - .status() - .expect("failed to generate alice.pem"); - assert!(status.success(), "sc-meta wallet test-wallet failed"); + generate_test_wallet(sc_meta_bin, "alice", &alice_pem_path); // Connect to the chain simulator. let interactor = Interactor::new(CHAIN_SIMULATOR_URL) .await .use_chain_simulator(true); + interactor.generate_blocks(10).await.unwrap(); // Register wallets – `register_wallet` automatically funds each account via // the chain simulator's `send_user_funds` endpoint. let alice_address = test_wallets::alice().to_address(); let bob_address = test_wallets::bob().to_address(); - // fund alice - interactor - .send_user_funds(&alice_address.to_bech32_default()) - .await - .unwrap(); - - // Allow the funding transactions to settle. - interactor.generate_blocks(10).await.unwrap(); - // ── balances before transfer ────────────────────────────────────────────── let alice_balance_before: u128 = interactor .get_account(&alice_address) @@ -350,15 +368,13 @@ async fn test_egld_transfer_alice_to_bob() { "--value", &TRANSFER_AMOUNT.to_string(), "--send", + "--wait-result", ]) .status() .expect("failed to execute sc-meta tx new"); assert!(status.success(), "sc-meta tx new command failed"); - // Allow the transfer transaction to settle. - interactor.generate_blocks(20).await.unwrap(); - // ── balances after transfer ─────────────────────────────────────────────── let alice_balance_after: u128 = interactor .get_account(&alice_address) @@ -384,9 +400,30 @@ async fn test_egld_transfer_alice_to_bob() { "Bob's balance did not increase by the expected transfer amount" ); - // Alice must have spent at least the transfer amount (gas fees are on top). + // Alice was funded automatically by the CLI, + // since the chain-simulator config was auto-detected, + // hence the additional FUND_AMOUNT. + // She must have spent at least the transfer amount (gas fees are on top). assert!( - alice_balance_before - alice_balance_after >= TRANSFER_AMOUNT, + alice_balance_before + FUND_AMOUNT - alice_balance_after >= TRANSFER_AMOUNT, "Alice's balance did not decrease by at least the transfer amount" ); } + +fn generate_test_wallet(sc_meta_bin: &str, name: &str, path: &std::path::Path) { + let status = Command::new(sc_meta_bin) + .args([ + "wallet", + "test-wallet", + "--name", + name, + "--path", + path.to_str().unwrap(), + ]) + .status() + .unwrap_or_else(|_| panic!("failed to generate {name}.pem")); + assert!( + status.success(), + "sc-meta wallet test-wallet failed for {name}" + ); +} diff --git a/framework/meta/tests/repro_build_test.rs b/framework/meta/tests/repro_build_test.rs index 567669377e..2ef11b44d9 100644 --- a/framework/meta/tests/repro_build_test.rs +++ b/framework/meta/tests/repro_build_test.rs @@ -20,6 +20,7 @@ use multiversx_sc_meta::{ folder_structure::{setup_workspace, strip_path}, }; use multiversx_sc_meta_lib::{ + cargo_toml::WorkspaceDependencies, cli::{BuildArgs, ContractCliAction}, tools::find_current_workspace, }; @@ -267,7 +268,11 @@ fn setup_build_dir(workspace: &Path, build_dir: &Path, contracts: &[&str]) { fs::remove_dir_all(&adder_interactor).unwrap(); } - strip_path(build_dir, &["target".to_string()]); + strip_path( + build_dir, + &["target".to_string()], + &WorkspaceDependencies::load_from_dir(workspace), + ); for contract in contracts { setup_workspace( diff --git a/framework/meta/tests/template_test.rs b/framework/meta/tests/template_test.rs index c6948e71cd..fc8669e3d0 100644 --- a/framework/meta/tests/template_test.rs +++ b/framework/meta/tests/template_test.rs @@ -25,7 +25,9 @@ fn test_template_list() { "adder".to_string(), "crypto-zombies".to_string(), "empty".to_string(), + "greeter".to_string(), "ping-pong-egld".to_string(), + "storage-examples".to_string(), ] ); } @@ -54,6 +56,12 @@ fn template_current_empty() { template_test_current("empty", TEMPLATE_TEST_CURRENT_SUB_PATH, "new-empty"); } +#[test] +#[cfg_attr(not(feature = "template-test-current"), ignore)] +fn template_current_greeter() { + template_test_current("greeter", TEMPLATE_TEST_CURRENT_SUB_PATH, "new-greeter"); +} + #[test] #[cfg_attr(not(feature = "template-test-current"), ignore)] fn template_current_ping_pong_egld() { @@ -64,6 +72,16 @@ fn template_current_ping_pong_egld() { ); } +#[test] +#[cfg_attr(not(feature = "template-test-current"), ignore)] +fn template_current_storage_examples() { + template_test_current( + "storage-examples", + TEMPLATE_TEST_CURRENT_SUB_PATH, + "new-storage-examples", + ); +} + #[test] #[cfg_attr(not(feature = "template-test-current"), ignore)] fn test_correct_naming() { @@ -132,11 +150,31 @@ fn setup_template_test_current( target } +fn check_files_exist(target: &ContractCreatorTarget, template_name: &str) { + assert!( + target.contract_dir().join("README.md").exists(), + "README.md not found in generated contract for template '{template_name}'" + ); + + assert!( + target.contract_dir().join("multiversx.json").exists(), + "multiversx.json not found in generated contract for template '{template_name}'" + ); + + assert!( + !target.contract_dir().join("mxsc-template.toml").exists(), + "mxsc-template.toml should not be present in generated contract for template '{template_name}'" + ); +} + /// Recreates the folder structure in `contracts`, on the same level. /// This way, the relative paths are still valid in this case, /// and we can test the templates with the framework version of the current branch. fn template_test_current(template_name: &str, sub_path: &str, new_name: &str) { let target = setup_template_test_current(template_name, sub_path, new_name); + + check_files_exist(&target, template_name); + if BUILD_CONTRACTS { build_contract(&target); } @@ -163,6 +201,26 @@ async fn template_released_empty() { template_test_released("empty", "released-empty").await; } +#[tokio::test] +#[ignore = "TODO: un-ignore after release"] +// #[cfg_attr(not(feature = "template-test-released"), ignore)] +async fn template_released_greeter() { + template_test_released("greeter", "released-greeter").await; +} + +#[tokio::test] +#[cfg_attr(not(feature = "template-test-released"), ignore)] +async fn template_released_ping_pong_egld() { + template_test_released("ping-pong-egld", "released-ping-pong-egld").await; +} + +#[tokio::test] +#[ignore = "TODO: un-ignore after release"] +// #[cfg_attr(not(feature = "template-test-released"), ignore)] +async fn template_released_storage_examples() { + template_test_released("storage-examples", "released-storage-examples").await; +} + /// These tests fully replicate the templating process. They /// - download the last released version of the repo, /// - create proper contracts, diff --git a/framework/meta/tests/wallet_convert_cli_test.rs b/framework/meta/tests/wallet_convert_cli_test.rs new file mode 100644 index 0000000000..2e2aa35bc5 --- /dev/null +++ b/framework/meta/tests/wallet_convert_cli_test.rs @@ -0,0 +1,217 @@ +use multiversx_sc_meta::cli::{WalletAction, WalletArgs, WalletConvertArgs}; +use multiversx_sc_meta::cmd::wallet_cmd::wallet; +use multiversx_sdk::wallet::Wallet; +use std::{ + fs, + io::Write, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +/// A well-known mnemonic with deterministic key material, taken from the wallet SDK tests. +const KNOWN_MNEMONIC: &str = "acid twice post genre topic observe valid viable gesture fortune funny dawn around blood enemy page update reduce decline van bundle zebra rookie real"; +/// Expected private key derived from `KNOWN_MNEMONIC` at account 0, index 0. +const KNOWN_PRIVATE_KEY: &str = "0b7966138e80b8f3bb64046f56aea4250fd7bacad6ed214165cea6767fd0bc2c"; +/// Expected public key derived from `KNOWN_MNEMONIC` at account 0, index 0. +const KNOWN_PUBLIC_KEY: &str = "dfefe0453840e5903f2bd519de9b0ed6e9621e57e28ba0b4c1b15115091dd72f"; +/// Expected bech32 address (default "erd" HRP) for the above key pair. +const KNOWN_BECH32_ERD: &str = "erd1mlh7q3fcgrjeq0et65vaaxcw6m5ky8jhu296pdxpk9g32zga6uhsemxx2a"; + +/// Base directory for temporary test artefacts (ignored by git). +const TEMP_DIR: &str = "tests/temp"; + +/// Returns a path inside `tests/temp/` using the given filename. +/// The directory is created on first use. +fn temp_path(filename: &str) -> PathBuf { + let dir = PathBuf::from(TEMP_DIR); + fs::create_dir_all(&dir).expect("failed to create tests/temp"); + dir.join(filename) +} + +/// Silently removes a file if it exists, ignoring errors. +fn remove_if_exists(path: &Path) { + let _ = fs::remove_file(path); +} + +// ── mnemonic → pem ─────────────────────────────────────────────────────────── + +/// Happy path: mnemonic from a file, PEM written to a file. +/// Verifies that the resulting PEM contains exactly the expected private key, +/// public key, and bech32 address. +#[test] +fn test_convert_mnemonic_to_pem_file_in_file_out() { + let mnemonic_file = temp_path("mnemonic_file_in_file_out.txt"); + let output_pem = temp_path("mnemonic_file_in_file_out.pem"); + remove_if_exists(&mnemonic_file); + remove_if_exists(&output_pem); + + fs::write(&mnemonic_file, KNOWN_MNEMONIC).unwrap(); + + wallet(&WalletArgs { + command: WalletAction::Convert(WalletConvertArgs { + from: "mnemonic".to_string(), + to: "pem".to_string(), + infile: Some(mnemonic_file.to_str().unwrap().to_string()), + outfile: Some(output_pem.to_str().unwrap().to_string()), + ..Default::default() + }), + }); + + let wallet = Wallet::from_pem_file(&output_pem).unwrap(); + assert_eq!(wallet.private_key_hex().unwrap(), KNOWN_PRIVATE_KEY); + assert_eq!(wallet.public_key().unwrap().to_hex(), KNOWN_PUBLIC_KEY); + + let pem_content = fs::read_to_string(&output_pem).unwrap(); + assert!( + pem_content.contains(KNOWN_BECH32_ERD), + "PEM header should contain the bech32 address" + ); + + remove_if_exists(&mnemonic_file); + remove_if_exists(&output_pem); +} + +/// Mnemonic piped via stdin, PEM returned on stdout. +/// Exercises the interactive (no --infile) code path with a controlled stdin. +/// This is the only test that must remain a subprocess invocation, because the +/// code path reads directly from `io::stdin()`. +#[test] +fn test_convert_mnemonic_to_pem_stdin_in_stdout() { + let sc_meta_bin = env!("CARGO_BIN_EXE_sc-meta"); + + let mut child = Command::new(sc_meta_bin) + .args([ + "wallet", + "convert", + "--in-format", + "mnemonic", + "--out-format", + "pem", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to spawn sc-meta"); + + // Write the mnemonic to the child's stdin, then drop to signal EOF. + if let Some(mut stdin) = child.stdin.take() { + stdin + .write_all(KNOWN_MNEMONIC.as_bytes()) + .expect("failed to write to stdin"); + } + + let output = child.wait_with_output().expect("failed to wait for child"); + assert!(output.status.success()); + + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(stdout.contains("-----BEGIN PRIVATE KEY for")); + assert!(stdout.contains(KNOWN_BECH32_ERD)); + assert!(stdout.contains("-----END PRIVATE KEY for")); +} + +/// Custom --hrp flag changes the bech32 prefix in the PEM header while keeping +/// the underlying key material identical. +#[test] +fn test_convert_mnemonic_to_pem_custom_hrp() { + let custom_hrp = "test"; + let mnemonic_file = temp_path("mnemonic_custom_hrp.txt"); + let output_pem = temp_path("mnemonic_custom_hrp.pem"); + remove_if_exists(&mnemonic_file); + remove_if_exists(&output_pem); + + fs::write(&mnemonic_file, KNOWN_MNEMONIC).unwrap(); + + wallet(&WalletArgs { + command: WalletAction::Convert(WalletConvertArgs { + from: "mnemonic".to_string(), + to: "pem".to_string(), + infile: Some(mnemonic_file.to_str().unwrap().to_string()), + outfile: Some(output_pem.to_str().unwrap().to_string()), + hrp: Some(custom_hrp.to_string()), + }), + }); + + let pem_content = fs::read_to_string(&output_pem).unwrap(); + // bech32 separator between HRP and data is always "1" + assert!( + pem_content.contains(&format!("-----BEGIN PRIVATE KEY for {custom_hrp}1")), + "PEM header should use the custom HRP; got:\n{pem_content}" + ); + + // Key material must be the same regardless of the HRP + let wallet = Wallet::from_pem_file(&output_pem).unwrap(); + assert_eq!(wallet.private_key_hex().unwrap(), KNOWN_PRIVATE_KEY); + assert_eq!(wallet.public_key_hex().unwrap(), KNOWN_PUBLIC_KEY); + + remove_if_exists(&mnemonic_file); + remove_if_exists(&output_pem); +} + +// ── unsupported format pair ─────────────────────────────────────────────────── + +/// Any conversion path that is not explicitly handled should return without +/// panicking (it prints "Unsupported conversion" to stdout). +#[test] +fn test_convert_unsupported_format_does_not_panic() { + // mnemonic → keystore-secret is not a supported path; should be a no-op. + wallet(&WalletArgs { + command: WalletAction::Convert(WalletConvertArgs { + from: "mnemonic".to_string(), + to: "keystore-secret".to_string(), + ..Default::default() + }), + }); +} + +// ── missing --infile errors ─────────────────────────────────────────────────── + +/// pem → keystore-secret requires --infile; omitting it must panic with an +/// informative message. +#[test] +fn test_convert_pem_to_keystore_panics_without_infile() { + let result = std::panic::catch_unwind(|| { + wallet(&WalletArgs { + command: WalletAction::Convert(WalletConvertArgs { + from: "pem".to_string(), + to: "keystore-secret".to_string(), + ..Default::default() + }), + }); + }); + let err = result.expect_err("expected a panic when --infile is missing"); + let msg = err + .downcast_ref::<&str>() + .copied() + .or_else(|| err.downcast_ref::().map(String::as_str)) + .unwrap_or(""); + assert!( + msg.contains("Input file is required for pem format"), + "unexpected panic message: {msg}" + ); +} + +/// keystore-secret → pem requires --infile; omitting it must panic with an +/// informative message. +#[test] +fn test_convert_keystore_to_pem_panics_without_infile() { + let result = std::panic::catch_unwind(|| { + wallet(&WalletArgs { + command: WalletAction::Convert(WalletConvertArgs { + from: "keystore-secret".to_string(), + to: "pem".to_string(), + ..Default::default() + }), + }); + }); + let err = result.expect_err("expected a panic when --infile is missing"); + let msg = err + .downcast_ref::<&str>() + .copied() + .or_else(|| err.downcast_ref::().map(String::as_str)) + .unwrap_or(""); + assert!( + msg.contains("Input file is required for keystore-secret format"), + "unexpected panic message: {msg}" + ); +} diff --git a/framework/meta/tests/wallet_encrypt_keystore_test.rs b/framework/meta/tests/wallet_encrypt_keystore_test.rs index 29d98fd718..a5e8596c07 100644 --- a/framework/meta/tests/wallet_encrypt_keystore_test.rs +++ b/framework/meta/tests/wallet_encrypt_keystore_test.rs @@ -1,5 +1,9 @@ +use multiversx_chain_core::std::Bech32Hrp; use multiversx_sc::types::Address; -use multiversx_sdk::{crypto::public_key::PublicKey, wallet::Wallet}; +use multiversx_sc_meta::cmd::wallet_cmd::new_keystore_randomness; +use multiversx_sdk::wallet::Keystore; +use multiversx_sdk::wallet::PrivateKey; +use multiversx_sdk::wallet::Wallet; use std::fs::{self, File}; use std::io::Write; @@ -8,56 +12,53 @@ const ALICE_KEYSTORE_PATH_TEST_1: &str = "tests/alice1.json"; const ALICE_KEYSTORE_PATH_TEST_2: &str = "tests/alice2.json"; const ALICE_PEM_PATH_TEST: &str = "tests/alice_test.pem"; const KEYSTORE_PASSWORD: &str = "abcd"; -const ALICE_PUBLIC_KEY: &str = "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; const ALICE_PRIVATE_KEY: &str = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; fn create_keystore_file_from_scratch(hrp: &str, file: &str) -> Address { - let wallet = Wallet::from_private_key(ALICE_PRIVATE_KEY).unwrap(); - let address = wallet.to_address(); - - let concatenated_keys = format!("{}{}", ALICE_PRIVATE_KEY, ALICE_PUBLIC_KEY); - let hex_decoded_keys = hex::decode(concatenated_keys).unwrap(); - let json_result = Wallet::encrypt_keystore( - hex_decoded_keys.as_slice(), - hrp, - &address, - ALICE_PUBLIC_KEY, + let wallet = Wallet::from(PrivateKey::from_seed_hex_str(ALICE_PRIVATE_KEY).unwrap()); + let json_result = Keystore::encrypt( + wallet.private_key().unwrap(), + hrp.try_into().expect("invalid HRP"), KEYSTORE_PASSWORD, - ); + new_keystore_randomness(), + ) + .to_json_string(); write_to_file(&json_result, file); - address + wallet.address } #[test] fn test_wallet_convert_pem_to_keystore() { let _ = create_keystore_file_from_scratch("erd", ALICE_KEYSTORE_PATH_TEST_1); - let (private_key_pem, _public_key_pem) = Wallet::get_wallet_keys_pem(ALICE_PEM_PATH); + let wallet_pem = Wallet::from_pem_file(ALICE_PEM_PATH).unwrap(); assert_eq!( - Wallet::get_private_key_from_keystore_secret(ALICE_KEYSTORE_PATH_TEST_1, KEYSTORE_PASSWORD) + Keystore::from_file(ALICE_KEYSTORE_PATH_TEST_1) + .unwrap() + .decrypt_wallet(KEYSTORE_PASSWORD) .unwrap() - .to_string(), - private_key_pem + .private_key() + .unwrap(), + wallet_pem.private_key().unwrap() ); fs::remove_file(ALICE_KEYSTORE_PATH_TEST_1).unwrap(); } #[test] fn test_wallet_convert_keystore_to_pem() { - let address = create_keystore_file_from_scratch("erd", ALICE_KEYSTORE_PATH_TEST_2); + create_keystore_file_from_scratch("erd", ALICE_KEYSTORE_PATH_TEST_2); - let private_key = - Wallet::get_private_key_from_keystore_secret(ALICE_KEYSTORE_PATH_TEST_2, KEYSTORE_PASSWORD) - .unwrap(); - let private_key_str = private_key.to_string(); - let public_key = PublicKey::from(&private_key); - let public_key_str = public_key.to_string(); - - let pem_content = - Wallet::generate_pem_content("erd", &address, &private_key_str, &public_key_str); + let wallet = Keystore::from_file(ALICE_KEYSTORE_PATH_TEST_2) + .unwrap() + .decrypt_wallet(KEYSTORE_PASSWORD) + .unwrap(); + let pem_content = wallet.to_pem(Bech32Hrp::default()).unwrap().to_pem_str(); write_to_file(&pem_content, ALICE_PEM_PATH_TEST); assert_eq!( - private_key_str, - Wallet::get_wallet_keys_pem(ALICE_PEM_PATH_TEST).0 + wallet.private_key().unwrap(), + Wallet::from_pem_file(ALICE_PEM_PATH_TEST) + .unwrap() + .private_key() + .unwrap() ); fs::remove_file(ALICE_PEM_PATH_TEST).unwrap(); diff --git a/framework/scenario/Cargo.toml b/framework/scenario/Cargo.toml index ff68ca20ea..13b85cebbe 100644 --- a/framework/scenario/Cargo.toml +++ b/framework/scenario/Cargo.toml @@ -35,7 +35,7 @@ log = { version = "0.4", features = ["std"] } serde = "1.0" serde_json = "1.0" pathdiff = "0.2" -itertools = "0.14" +itertools = "0.15" colored = "3.0" unwrap-infallible = "1.0" simple-error = "0.3" diff --git a/framework/scenario/src/api/impl_vh/vh_single_tx_api.rs b/framework/scenario/src/api/impl_vh/vh_single_tx_api.rs index b07f3063c6..10038e9afc 100644 --- a/framework/scenario/src/api/impl_vh/vh_single_tx_api.rs +++ b/framework/scenario/src/api/impl_vh/vh_single_tx_api.rs @@ -12,7 +12,7 @@ use multiversx_chain_vm::{ vm_hooks::VMHooksContext, }, schedule::GasSchedule, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, }; use crate::executor::debug::ContractDebugInstanceState; @@ -92,14 +92,13 @@ impl VMHooksContext for SingleTxApiVMHooksContext { } fn storage_read_any_address(&self, address: &Address, key: &[u8]) -> Vec { - self.0.with_account_mut(address, |account| { - account.storage.get(key).cloned().unwrap_or_default() - }) + self.0 + .with_account_mut(address, |account| account.storage_get(key)) } fn storage_write(&mut self, key: &[u8], value: &[u8]) -> Result<(), VMHooksEarlyExit> { self.0.with_account_mut(&self.0.tx_input_box.to, |account| { - account.storage.insert(key.to_vec(), value.to_vec()); + account.storage_set(key, value); }); Ok(()) } @@ -153,7 +152,7 @@ impl VMHooksContext for SingleTxApiVMHooksContext { &mut self, _egld_value: num_bigint::BigUint, _contract_code: Vec, - _code_metadata: VMCodeMetadata, + _code_metadata: CodeMetadata, _args: Vec>, ) -> Result<(Address, Vec>), VMHooksEarlyExit> { panic!("cannot launch contract calls in the SingleTxApi") diff --git a/framework/scenario/src/api/impl_vh/vh_static_api.rs b/framework/scenario/src/api/impl_vh/vh_static_api.rs index a5efd55b1d..bb3a670c67 100644 --- a/framework/scenario/src/api/impl_vh/vh_static_api.rs +++ b/framework/scenario/src/api/impl_vh/vh_static_api.rs @@ -9,7 +9,7 @@ use multiversx_chain_vm::{ vm_hooks::VMHooksContext, }, schedule::GasSchedule, - types::{Address, VMCodeMetadata}, + types::{Address, CodeMetadata}, }; use crate::executor::debug::ContractDebugInstanceState; @@ -132,7 +132,7 @@ impl VMHooksContext for StaticApiVMHooksContext { &mut self, _egld_value: num_bigint::BigUint, _contract_code: Vec, - _code_metadata: VMCodeMetadata, + _code_metadata: CodeMetadata, _args: Vec>, ) -> Result<(Address, Vec>), VMHooksEarlyExit> { panic!("cannot launch contract calls in the StaticApi") diff --git a/framework/scenario/src/facade/world_tx/scenario_exec_call.rs b/framework/scenario/src/facade/world_tx/scenario_exec_call.rs index c0dd8757a9..a0dd10ae20 100644 --- a/framework/scenario/src/facade/world_tx/scenario_exec_call.rs +++ b/framework/scenario/src/facade/world_tx/scenario_exec_call.rs @@ -2,8 +2,8 @@ use multiversx_sc::{ tuple_util::NestedTupleFlatten, types::{ Code, FunctionCall, ManagedAddress, ManagedBuffer, NotPayable, RHListExec, Tx, - TxBaseWithEnv, TxEnv, TxEnvMockDeployAddress, TxEnvWithTxHash, TxFromSpecified, TxGas, - TxId, TxPayment, TxToSpecified, UpgradeCall, heap::H256, + TxBaseWithEnv, TxEnv, TxEnvMockDeployAddress, TxEnvWithRelayer, TxEnvWithTxHash, + TxFromSpecified, TxGas, TxId, TxPayment, TxToSpecified, UpgradeCall, heap::H256, }, }; @@ -115,6 +115,16 @@ where } } +impl TxEnvWithRelayer for ScenarioEnvExec<'_> { + fn set_relayer_address(&mut self, relayer: ManagedAddress) { + self.data.set_relayer_address(relayer); + } + + fn take_relayer_address(&mut self) -> Option> { + self.data.take_relayer_address() + } +} + impl TxEnvWithTxHash for ScenarioEnvExec<'_> { fn set_tx_id(&mut self, tx_id: TxId) { self.data.set_tx_id(tx_id); diff --git a/framework/scenario/src/facade/world_tx/scenario_tx_env.rs b/framework/scenario/src/facade/world_tx/scenario_tx_env.rs index 9d1b867dfb..f844a35adc 100644 --- a/framework/scenario/src/facade/world_tx/scenario_tx_env.rs +++ b/framework/scenario/src/facade/world_tx/scenario_tx_env.rs @@ -1,5 +1,7 @@ use multiversx_chain_scenario_format::interpret_trait::InterpreterContext; -use multiversx_sc::types::{H256, ManagedAddress, ManagedBuffer, TxEnv, TxEnvWithTxHash, TxId}; +use multiversx_sc::types::{ + H256, ManagedAddress, ManagedBuffer, TxEnv, TxEnvWithRelayer, TxEnvWithTxHash, TxId, +}; use crate::{ScenarioWorld, api::StaticApi, scenario_model::TxExpect}; @@ -14,6 +16,7 @@ pub struct ScenarioTxEnvData { pub interpreter_context: InterpreterContext, pub tx_id: Option, pub tx_hash: Option, + pub relayer: Option>, } impl TxEnv for ScenarioTxEnvData { @@ -54,6 +57,17 @@ impl TxEnvWithTxHash for ScenarioTxEnvData { } } +impl TxEnvWithRelayer for ScenarioTxEnvData { + fn set_relayer_address(&mut self, relayer: ManagedAddress) { + assert!(self.relayer.is_none(), "relayer set twice"); + self.relayer = Some(relayer); + } + + fn take_relayer_address(&mut self) -> Option> { + core::mem::take(&mut self.relayer) + } +} + impl ScenarioTxEnvData { pub fn interpreter_context(&self) -> InterpreterContext { self.interpreter_context.clone() @@ -74,6 +88,7 @@ impl ScenarioWorld { .with_allowed_missing_files(), tx_id: None, tx_hash: None, + relayer: None, } } } diff --git a/framework/scenario/src/imports.rs b/framework/scenario/src/imports.rs index 64b3d06399..4ba2cdf88e 100644 --- a/framework/scenario/src/imports.rs +++ b/framework/scenario/src/imports.rs @@ -18,7 +18,7 @@ pub use crate::{ model::{ Account, AddressValue, BytesValue, CheckAccount, CheckStateStep, ScCallStep, ScDeployStep, ScQueryStep, Scenario, SetStateStep, TransferStep, TxESDT, TxExpect, - TypedResponse, + TxResponseStatus, TypedResponse, }, run_vm::ExecutorConfig, }, diff --git a/framework/scenario/src/scenario/model/transaction/tx_call.rs b/framework/scenario/src/scenario/model/transaction/tx_call.rs index 963e7f92bd..79aa2213ec 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_call.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_call.rs @@ -25,6 +25,7 @@ pub struct TxCall { pub arguments: Vec, pub gas_limit: U64Value, pub gas_price: U64Value, + pub relayer: Option, } impl Default for TxCall { @@ -38,6 +39,7 @@ impl Default for TxCall { arguments: Default::default(), gas_limit: U64Value::from(DEFAULT_GAS_EXPR), gas_price: Default::default(), + relayer: None, } } } @@ -61,6 +63,7 @@ impl InterpretableFrom for TxCall { .collect(), gas_limit: U64Value::interpret_from(from.gas_limit, context), gas_price: U64Value::interpret_from(from.gas_price.unwrap_or_default(), context), + relayer: None, } } } @@ -154,6 +157,7 @@ impl TxCall { arguments, gas_limit: self.gas_limit.clone(), gas_price: self.gas_price.clone(), + relayer: self.relayer.clone(), } } diff --git a/framework/scenario/src/scenario/model/transaction/tx_deploy.rs b/framework/scenario/src/scenario/model/transaction/tx_deploy.rs index b497265eb0..e34a9df1ee 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_deploy.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_deploy.rs @@ -18,6 +18,7 @@ pub struct TxDeploy { pub arguments: Vec, pub gas_limit: U64Value, pub gas_price: U64Value, + pub relayer: Option, } impl Default for TxDeploy { @@ -25,11 +26,12 @@ impl Default for TxDeploy { Self { from: Default::default(), egld_value: Default::default(), - code_metadata: CodeMetadata::all(), + code_metadata: CodeMetadata::default(), contract_code: Default::default(), arguments: Default::default(), gas_limit: U64Value::from(DEFAULT_GAS_EXPR), gas_price: Default::default(), + relayer: None, } } } @@ -48,6 +50,7 @@ impl InterpretableFrom for TxDeploy { .collect(), gas_limit: U64Value::interpret_from(from.gas_limit, context), gas_price: U64Value::interpret_from(from.gas_price.unwrap_or_default(), context), + relayer: None, } } } diff --git a/framework/scenario/src/scenario/model/transaction/tx_transfer.rs b/framework/scenario/src/scenario/model/transaction/tx_transfer.rs index 468ef876d7..01801ff99b 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_transfer.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_transfer.rs @@ -16,6 +16,7 @@ pub struct TxTransfer { pub esdt_value: Vec, pub gas_limit: U64Value, pub gas_price: U64Value, + pub relayer: Option, } impl InterpretableFrom for TxTransfer { @@ -31,6 +32,7 @@ impl InterpretableFrom for TxTransfer { .collect(), gas_limit: U64Value::interpret_from(from.gas_limit.unwrap_or_default(), context), gas_price: U64Value::interpret_from(from.gas_price.unwrap_or_default(), context), + relayer: None, } } } @@ -65,6 +67,7 @@ impl TxTransfer { arguments: Vec::new(), gas_limit: self.gas_limit.clone(), gas_price: self.gas_price.clone(), + relayer: self.relayer.clone(), } } } diff --git a/framework/scenario/src/scenario/model/value/bytes_value.rs b/framework/scenario/src/scenario/model/value/bytes_value.rs index 752df8f41c..1a8d8566c7 100644 --- a/framework/scenario/src/scenario/model/value/bytes_value.rs +++ b/framework/scenario/src/scenario/model/value/bytes_value.rs @@ -1,5 +1,5 @@ use multiversx_sc::{ - codec::{EncodeError, TopEncode, TopEncodeOutput}, + codec::{EncodeErrorHandler, TopEncode, TopEncodeOutput}, types::{AnnotatedValue, ManagedBuffer, TxCodeValue, TxEnv}, }; @@ -163,11 +163,12 @@ impl fmt::Display for BytesValue { } impl TopEncode for BytesValue { - fn top_encode(&self, output: O) -> Result<(), EncodeError> + fn top_encode_or_handle_err(&self, output: O, h: H) -> Result<(), H::HandledErr> where O: TopEncodeOutput, + H: EncodeErrorHandler, { - self.value.top_encode(output) + self.value.top_encode_or_handle_err(output, h) } } diff --git a/framework/scenario/src/scenario/run_vm/check_state.rs b/framework/scenario/src/scenario/run_vm/check_state.rs index 2a70013fba..f5bbdc2106 100644 --- a/framework/scenario/src/scenario/run_vm/check_state.rs +++ b/framework/scenario/src/scenario/run_vm/check_state.rs @@ -78,19 +78,15 @@ fn execute(state: &BlockchainState, accounts: &CheckAccounts) { ); if let CheckStorage::Equal(eq) = &expected_account.storage { - let default_value = &Vec::new(); for (expected_key, expected_value) in eq.storages.iter() { - let actual_value = account - .storage - .get(&expected_key.value) - .unwrap_or(default_value); + let actual_value = account.storage_get(&expected_key.value); assert!( - expected_value.check(actual_value), + expected_value.check(&actual_value), "bad storage value. Address: {}. Key: {}. Want: {}. Have: {}", expected_address, expected_key, expected_value, - verbose_hex(actual_value) + verbose_hex(&actual_value) ); } diff --git a/framework/scenario/src/scenario/run_vm/sc_deploy.rs b/framework/scenario/src/scenario/run_vm/sc_deploy.rs index f6f1c4eebd..fbfbb38720 100644 --- a/framework/scenario/src/scenario/run_vm/sc_deploy.rs +++ b/framework/scenario/src/scenario/run_vm/sc_deploy.rs @@ -2,13 +2,10 @@ use crate::{ multiversx_sc::types::heap::Address, scenario::model::ScDeployStep, scenario_model::TxResponse, }; -use multiversx_chain_vm::{ - host::{ - context::{TxFunctionName, TxInput, TxResult}, - execution, - runtime::{RuntimeInstanceCallLambda, RuntimeInstanceCallLambdaDefault}, - }, - types::VMCodeMetadata, +use multiversx_chain_vm::host::{ + context::{TxFunctionName, TxInput, TxResult}, + execution, + runtime::{RuntimeInstanceCallLambda, RuntimeInstanceCallLambdaDefault}, }; use super::{ScenarioVMRunner, check_tx_output, tx_input_util::generate_tx_hash}; @@ -42,7 +39,7 @@ impl ScenarioVMRunner { let (new_address, tx_result) = execution::commit_deploy( tx_input, contract_code, - VMCodeMetadata::from(sc_deploy_step.tx.code_metadata.bits()), + sc_deploy_step.tx.code_metadata, &mut self.blockchain_mock.state, &runtime, f, diff --git a/framework/scenario/src/scenario/run_vm/set_state.rs b/framework/scenario/src/scenario/run_vm/set_state.rs index 6bcb366870..6ff84d8b68 100644 --- a/framework/scenario/src/scenario/run_vm/set_state.rs +++ b/framework/scenario/src/scenario/run_vm/set_state.rs @@ -5,15 +5,12 @@ use multiversx_chain_vm::{ AccountData, AccountEsdt, BlockInfo as CrateBlockInfo, BlockchainState, EsdtData, EsdtInstance, EsdtInstanceMetadata, EsdtInstances, EsdtRoles, }, - types::VMCodeMetadata, + types::CodeMetadata, }; use multiversx_sc::types::{TimestampMillis, TimestampSeconds}; use super::ScenarioVMRunner; -/// Refers to the default of the "setState" scenario step. -pub const DEFAULT_CODE_METADATA: VMCodeMetadata = VMCodeMetadata::all(); - impl ScenarioVMRunner { pub fn perform_set_state(&mut self, set_state_step: &SetStateStep) { execute(&mut self.blockchain_mock.state, set_state_step); @@ -61,8 +58,10 @@ fn execute(state: &mut BlockchainState, set_state_step: &SetStateStep) { code_metadata: account .code_metadata .as_ref() - .map(|bytes_value| VMCodeMetadata::from(&bytes_value.value)) - .unwrap_or(DEFAULT_CODE_METADATA), + .map(|bytes_value| { + CodeMetadata::try_from(&bytes_value.value).expect("invalid CodeMetadata") + }) + .unwrap_or_default(), contract_owner: account .owner .as_ref() diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs index 2f7cf36199..a6360abbd0 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs @@ -1,6 +1,6 @@ use multiversx_sc::types::{ - FunctionCall, RHListExec, Tx, TxEnv, TxEnvWithTxHash, TxFromSpecified, TxGas, TxPayment, - TxToSpecified, + FunctionCall, RHListExec, Tx, TxEnv, TxEnvWithRelayer, TxEnvWithTxHash, TxFromSpecified, TxGas, + TxPayment, TxToSpecified, }; use crate::scenario_model::{ScCallStep, TxESDT, TxExpect, TxResponse}; @@ -10,7 +10,7 @@ use super::{StepWrapper, TxToStep, address_annotated, gas_annotated}; impl TxToStep for Tx, RH> where - Env: TxEnvWithTxHash, + Env: TxEnvWithTxHash + TxEnvWithRelayer, From: TxFromSpecified, To: TxToSpecified, Payment: TxPayment, @@ -30,6 +30,10 @@ where ); step.tx_id = self.env.take_tx_id(); step.explicit_tx_hash = self.env.take_tx_hash(); + step.tx.relayer = self + .env + .take_relayer_address() + .map(|a| address_annotated(&self.env, &a)); step.expect = Some(self.result_handler.list_preprocessing()); StepWrapper { diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs index d04856daad..d1856e30d2 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs @@ -1,6 +1,6 @@ use multiversx_sc::types::{ - Code, DeployCall, RHListExec, Tx, TxCodeValue, TxEnv, TxEnvWithTxHash, TxFromSpecified, TxGas, - TxPayment, + Code, DeployCall, RHListExec, Tx, TxCodeValue, TxEnv, TxEnvWithRelayer, TxEnvWithTxHash, + TxFromSpecified, TxGas, TxPayment, }; use crate::scenario_model::{ScDeployStep, TxExpect, TxResponse}; @@ -10,7 +10,7 @@ use super::{StepWrapper, TxToStep, address_annotated, code_annotated, gas_annota impl TxToStep for Tx>, RH> where - Env: TxEnvWithTxHash, + Env: TxEnvWithTxHash + TxEnvWithRelayer, From: TxFromSpecified, Payment: TxPayment, Gas: TxGas, @@ -24,6 +24,10 @@ where tx_to_sc_deploy_step(&self.env, self.from, self.payment, self.gas, self.data); step.tx_id = self.env.take_tx_id(); step.explicit_tx_hash = self.env.take_tx_hash(); + step.tx.relayer = self + .env + .take_relayer_address() + .map(|a| address_annotated(&self.env, &a)); step.expect = Some(self.result_handler.list_preprocessing()); StepWrapper { diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_transfer.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_transfer.rs index 28e3aa7dc2..89a2a76ed2 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_transfer.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_transfer.rs @@ -1,4 +1,6 @@ -use multiversx_sc::types::{Tx, TxEnv, TxFromSpecified, TxGas, TxPayment, TxToSpecified}; +use multiversx_sc::types::{ + Tx, TxEnv, TxEnvWithRelayer, TxFromSpecified, TxGas, TxPayment, TxToSpecified, +}; use crate::{imports::TxESDT, scenario_model::TransferStep}; @@ -6,7 +8,7 @@ use super::{StepWrapper, TxToStep, address_annotated, gas_annotated}; impl TxToStep for Tx where - Env: TxEnv, + Env: TxEnv + TxEnvWithRelayer, From: TxFromSpecified, To: TxToSpecified, Payment: TxPayment, @@ -14,8 +16,12 @@ where { type Step = TransferStep; - fn tx_to_step(self) -> StepWrapper { - let step = tx_to_transfer_step(&self.env, self.from, self.to, self.payment, self.gas); + fn tx_to_step(mut self) -> StepWrapper { + let mut step = tx_to_transfer_step(&self.env, self.from, self.to, self.payment, self.gas); + step.tx.relayer = self + .env + .take_relayer_address() + .map(|a| address_annotated(&self.env, &a)); StepWrapper { env: self.env, diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_upgrade.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_upgrade.rs index d646fbf7d6..4c96e77a45 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_upgrade.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_upgrade.rs @@ -1,6 +1,6 @@ use multiversx_sc::types::{ - Code, NotPayable, RHListExec, Tx, TxCodeValue, TxEnv, TxFromSpecified, TxGas, TxToSpecified, - UpgradeCall, + Code, NotPayable, RHListExec, Tx, TxCodeValue, TxEnv, TxEnvWithRelayer, TxFromSpecified, TxGas, + TxToSpecified, UpgradeCall, }; use crate::{ @@ -13,7 +13,7 @@ use super::{StepWrapper, TxToStep, address_annotated, code_annotated, gas_annota impl TxToStep for Tx>, RH> where - Env: TxEnv, + Env: TxEnv + TxEnvWithRelayer, From: TxFromSpecified, To: TxToSpecified, Gas: TxGas, @@ -22,9 +22,13 @@ where { type Step = ScCallStep; - fn tx_to_step(self) -> StepWrapper { + fn tx_to_step(mut self) -> StepWrapper { let mut step = tx_to_sc_call_upgrade_step(&self.env, self.from, self.to, self.gas, self.data); + step.tx.relayer = self + .env + .take_relayer_address() + .map(|a| address_annotated(&self.env, &a)); step.expect = Some(self.result_handler.list_preprocessing()); StepWrapper { diff --git a/framework/scenario/tests/single_tx_api_test.rs b/framework/scenario/tests/single_tx_api_test.rs index 35bee42e5f..ac38737071 100644 --- a/framework/scenario/tests/single_tx_api_test.rs +++ b/framework/scenario/tests/single_tx_api_test.rs @@ -17,13 +17,11 @@ fn single_tx_api_test() { // check directly in storage SingleTxApi::with_global_default_account(|account| { - let value = account.storage.get(storage_key.as_bytes()).unwrap(); - assert_eq!(value, &vec![5u8]); + let value = account.storage_get(storage_key.as_bytes()); + assert_eq!(value, vec![5u8]); // change value directly in storage - account - .storage - .insert(storage_key.as_bytes().to_vec(), vec![7u8]); + account.storage_set(storage_key.as_bytes(), &[7u8]); }); // read again @@ -37,11 +35,7 @@ fn single_tx_api_test() { // checking directly in storage SingleTxApi::with_global_default_account(|account| { - let value = account - .storage - .get(storage_key.as_bytes()) - .cloned() - .unwrap_or_default(); + let value = account.storage_get(storage_key.as_bytes()); assert!(value.is_empty()); }); } diff --git a/framework/scenario/tests/test_hash_map_storage_mapper.rs b/framework/scenario/tests/test_hash_map_storage_mapper.rs index a6b9981b7f..152b3c407b 100644 --- a/framework/scenario/tests/test_hash_map_storage_mapper.rs +++ b/framework/scenario/tests/test_hash_map_storage_mapper.rs @@ -1,12 +1,17 @@ use multiversx_sc::storage::{ StorageKey, - mappers::{MapMapper, MapStorageMapper, StorageClearable, StorageMapper}, + mappers::{MapMapper, MapStorageMapper, StorageClearable, StorageMapper, VecMapper}, }; use multiversx_sc_scenario::api::SingleTxApi; +fn create_map_storage_custom_key( + key: &[u8], +) -> MapStorageMapper> { + MapStorageMapper::new(StorageKey::new(key)) +} + fn create_map_storage() -> MapStorageMapper> { - let base_key = StorageKey::new(&b"my_map_storage"[..]); - MapStorageMapper::new(base_key) + create_map_storage_custom_key(b"my_map_storage") } #[test] @@ -55,3 +60,88 @@ fn test_map_storage_clear() { assert_eq!(map.len(), 0); assert!(map.is_empty()); } + +/// `MapStorageMapper::remove` must fully clear the nested storage namespace. +/// A mapper handle obtained before removal reflects the cleared state because it +/// points to the same deterministic storage prefix. +#[test] +fn test_remove_clears_nested_storage() { + let mut map = create_map_storage_custom_key(b"remove_clears_test"); + + map.insert_default(10); + let mut nested = map.get(&10).unwrap(); + nested.insert(1, 100); + nested.insert(2, 200); + assert_eq!(nested.len(), 2); + + // remove() must clear the nested storage, not just drop the keys_set entry. + assert!(map.remove(&10)); + assert!(!map.contains_key(&10)); + + // The previously obtained handle still points to the same prefix; it should now be empty. + assert_eq!(nested.len(), 0); + assert!(nested.is_empty()); +} + +/// `OccupiedEntry` manipulation via `or_insert_default` followed by `MapStorageMapper::remove` +#[test] +fn test_entry_remove_clears_nested_storage() { + let mut map = create_map_storage_custom_key(b"entry_remove_clears_test"); + + // Use the entry API to insert and populate the nested mapper. + let occupied = map.entry(20).or_insert_default(); + let mut nested = occupied.get(); + nested.insert(3, 300); + assert_eq!(nested.len(), 1); + + // Remove via MapStorageMapper::remove (which OccupiedEntry::remove delegates to). + assert!(map.remove(&20)); + assert!(!map.contains_key(&20)); + + // Nested storage must be cleared; the previously obtained handle reflects this. + assert_eq!(nested.len(), 0); +} + +/// `insert_default` must clear any residual nested storage so that a (re-)inserted key +/// always starts with an empty nested mapper. +/// +/// This test injects a stale `.len` value directly into the raw storage backend to +/// simulate residual data at the nested `VecMapper` namespace, bypassing the mapper's +/// own write path. `VecMapper` stores its length at `base_key + ".len"` as a +/// top-encoded `usize` (minimal big-endian, so `1` is `[0x01]`). +/// +/// Without the fix, `insert_default` would leave that data intact and the nested mapper +/// would report a non-zero length. With the fix, `insert_default` calls `clear()` on +/// the nested mapper before inserting the key, wiping the stale length. +#[test] +fn test_insert_default_clears_residual_nested_storage() { + // Unique base key for this test to avoid interfering with other tests. + let base_key = b"stale_state_test_vec"; + + // Build the raw storage key for the nested VecMapper's `.len` slot. + // Layout: + b".storage" + NestedEncode(99u64) + b".len" + // NestedEncode of u64 is 8 big-endian bytes. + let mut nested_base_key = StorageKey::new(&base_key[..]); + nested_base_key.append_bytes(b".storage"); + nested_base_key.append_item(&99u64); + + let mut stale_vec = VecMapper::::new(nested_base_key); + stale_vec.push(&123u64); + assert_eq!(stale_vec.len(), 1); + + // Now insert key 99 via the public API using a VecMapper as the nested type. + // The fix ensures insert_default clears the nested namespace before registering + // the key, so the nested mapper is empty regardless of any residual data. + let mut map = MapStorageMapper::>::new( + StorageKey::new(&base_key[..]), + ); + assert!(map.insert_default(99)); + + let nested = map.get(&99).unwrap(); + assert_eq!( + nested.len(), + 0, + "insert_default must clear residual nested storage; nested mapper must start empty" + ); + assert!(nested.is_empty()); +} diff --git a/framework/scenario/tests/test_queue_mapper.rs b/framework/scenario/tests/test_queue_mapper.rs index b32e28e3c3..5c91b7d82c 100644 --- a/framework/scenario/tests/test_queue_mapper.rs +++ b/framework/scenario/tests/test_queue_mapper.rs @@ -100,3 +100,25 @@ fn test_queue_clear() { assert_eq!(queue.len(), 0); assert!(queue.is_empty()); } + +#[test] +fn test_queue_clear_no_info_leak() { + SingleTxApi::clear_global(); + + let mut queue = create_queue(); + queue.push_back(1u64); + queue.push_back(2u64); + queue.push_back(3u64); + queue.clear(); + + // After clearing, the ".info" storage slot must be fully deleted (not merely + // reset to a default-encoded value), so no residual key should remain. + let info_key = b"my_queue.info".to_vec(); + let no_residual = SingleTxApi::with_global_default_account(|account| { + account.storage_get(&info_key).is_empty() + }); + assert!( + no_residual, + "QueueMapper::clear() left a residual '.info' storage slot" + ); +} diff --git a/framework/scenario/tests/token_id_test.rs b/framework/scenario/tests/token_id_test.rs index 700057ff73..1524e1efad 100644 --- a/framework/scenario/tests/token_id_test.rs +++ b/framework/scenario/tests/token_id_test.rs @@ -79,6 +79,12 @@ fn test_is_valid_esdt_identifier() { // valid ticker only numbers assert!(TokenId::::from("12345-6258d2").is_valid_esdt_identifier()); + // valid identifier with max-length ticker (10 chars) + assert!(TokenId::::from("EGLDRIDEFL-08d8ef").is_valid_esdt_identifier()); + + // valid identifier with number at end of max-length ticker + assert!(TokenId::::from("EGLDRIDEF2-08d8ef").is_valid_esdt_identifier()); + // missing dash assert!(!TokenId::::from("ALC6258d2").is_valid_esdt_identifier()); @@ -88,12 +94,35 @@ fn test_is_valid_esdt_identifier() { // lowercase ticker assert!(!TokenId::::from("alc-6258d2").is_valid_esdt_identifier()); + // lowercase char in ticker + assert!(!TokenId::::from("EGLDRIDEFl-08d8ef").is_valid_esdt_identifier()); + + // special char in ticker + assert!(!TokenId::::from("EGLDRIDEF*-08d8ef").is_valid_esdt_identifier()); + // uppercase random chars assert!(!TokenId::::from("ALC-6258D2").is_valid_esdt_identifier()); + // uppercase char in random part + assert!(!TokenId::::from("EGLDRIDEFL-08d8eF").is_valid_esdt_identifier()); + + // special char in random part + assert!(!TokenId::::from("EGLDRIDEFL-08d*ef").is_valid_esdt_identifier()); + + // non-hex lowercase letter in random part (g-z are not valid hex digits) + assert!(!TokenId::::from("ABC-ghijkl").is_valid_esdt_identifier()); + assert!(!TokenId::::from("ALC-6258g2").is_valid_esdt_identifier()); + assert!(!TokenId::::from("ALC-zzzzzz").is_valid_esdt_identifier()); + // too many random chars assert!(!TokenId::::from("ALC-6258d2ff").is_valid_esdt_identifier()); + // too many random chars (7) + assert!(!TokenId::::from("EGLDRIDEFL-08d8eff").is_valid_esdt_identifier()); + + // too few random chars (5) + assert!(!TokenId::::from("EGLDRIDEFL-08d8e").is_valid_esdt_identifier()); + // ticker too short assert!(!TokenId::::from("AL-6258d2").is_valid_esdt_identifier()); diff --git a/framework/snippets/.gitignore b/framework/snippets/.gitignore new file mode 100644 index 0000000000..8867508db8 --- /dev/null +++ b/framework/snippets/.gitignore @@ -0,0 +1,2 @@ +# Test output +set_state.json diff --git a/framework/snippets/Cargo.toml b/framework/snippets/Cargo.toml index 1d103d09f9..406c9d1b45 100644 --- a/framework/snippets/Cargo.toml +++ b/framework/snippets/Cargo.toml @@ -17,6 +17,8 @@ categories = ["cryptography::cryptocurrencies"] default = ["http"] http = ["multiversx-sdk-http", "tokio"] dapp = ["multiversx-sdk-dapp"] +chain-simulator-tests = [] +ledger = ["multiversx-sdk/ledger"] [dependencies] hex = "0.4" @@ -24,8 +26,11 @@ log = "0.4" env_logger = "0.11" futures = "0.3" anyhow = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +toml = "1.0" colored = "3.0" +pathdiff = "0.2" tokio = { version = "1.24", features = ["full"], optional = true } @@ -51,3 +56,6 @@ default-features = false version = "=0.16.1" path = "../../sdk/dapp" optional = true + +[dev-dependencies] +serial_test = "3.2" diff --git a/framework/snippets/src/config.rs b/framework/snippets/src/config.rs new file mode 100644 index 0000000000..1461c5cc9a --- /dev/null +++ b/framework/snippets/src/config.rs @@ -0,0 +1,13 @@ +mod auto_save; +mod chain_type; +mod config_path; +mod connection_config; +mod interactor_config; +mod wallet_config; + +pub use auto_save::*; +pub use chain_type::ChainType; +pub use config_path::ConfigPath; +pub use connection_config::ConnectionConfig; +pub use interactor_config::{InteractorConfig, load_toml_config}; +pub use wallet_config::WalletConfig; diff --git a/framework/snippets/src/config/auto_save.rs b/framework/snippets/src/config/auto_save.rs new file mode 100644 index 0000000000..a609852429 --- /dev/null +++ b/framework/snippets/src/config/auto_save.rs @@ -0,0 +1,81 @@ +use std::{ + io::Write, + ops::{Deref, DerefMut}, + path::PathBuf, +}; + +use serde::Serialize; + +/// RAII wrapper that serialises its value to a TOML file on drop. +/// +/// Saving can be disabled by constructing with [`AutoSave::no_save`] or by +/// calling [`AutoSave::disable`], which is useful in tests where you don't want +/// side-effects on disk. +pub struct AutoSave { + value: T, + /// `None` means saving is disabled. + path: Option, +} + +impl AutoSave { + /// Wraps `value` and saves to `path` on drop. + pub fn new(value: T, path: impl Into) -> Self { + AutoSave { + value, + path: Some(path.into()), + } + } + + /// Wraps `value` without saving on drop. + pub fn no_save(value: T) -> Self { + AutoSave { value, path: None } + } + + /// Wraps the default value without saving on drop. + pub fn no_save_default() -> Self + where + T: Default, + { + AutoSave::no_save(T::default()) + } + + /// Disables saving for this instance. + pub fn disable(&mut self) { + self.path = None; + } + + /// Serialises the value to the configured path immediately. + /// + /// Returns `Ok(())` if saving is disabled (no-op) or if the write + /// succeeds. Returns `Err` on serialisation or I/O failure. + pub fn save(&self) -> Result<(), Box> { + if let Some(path) = &self.path { + let content = toml::to_string(&self.value)?; + let mut file = std::fs::File::create(path)?; + file.write_all(content.as_bytes())?; + } + Ok(()) + } +} + +impl Deref for AutoSave { + type Target = T; + + fn deref(&self) -> &T { + &self.value + } +} + +impl DerefMut for AutoSave { + fn deref_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl Drop for AutoSave { + fn drop(&mut self) { + if let Err(e) = self.save() { + eprintln!("AutoSave: failed to write state: {e}"); + } + } +} diff --git a/framework/snippets/src/config/chain_type.rs b/framework/snippets/src/config/chain_type.rs new file mode 100644 index 0000000000..e77edf3f8c --- /dev/null +++ b/framework/snippets/src/config/chain_type.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +/// Specifies to an interactor whether or not to activate additional chain simulator features, +/// e.g. the ability to set state for accounts, generate blocks, etc. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ChainType { + Real, + Simulator, +} diff --git a/framework/snippets/src/config/config_path.rs b/framework/snippets/src/config/config_path.rs new file mode 100644 index 0000000000..2e2e88e87f --- /dev/null +++ b/framework/snippets/src/config/config_path.rs @@ -0,0 +1,173 @@ +use std::{ + cell::RefCell, + fmt, + ops::Deref, + path::{Path, PathBuf}, +}; + +use multiversx_chain_scenario_format::value_interpreter::interpret_string; +use multiversx_sc_scenario::ScenarioTxEnv; +use multiversx_sc_scenario::multiversx_sc::types::{AnnotatedValue, ManagedBuffer, TxCodeValue}; +use serde::Deserialize; + +thread_local! { + /// Set by [`load_toml_config`] to the parent directory of the config file + /// before deserialization begins, then cleared afterwards. + /// + /// [`ConfigPath`]'s [`Deserialize`] impl reads this to resolve relative + /// paths at deserialization time, with no per-config boilerplate needed. + pub static CONFIG_BASE_DIR: RefCell> = const { RefCell::new(None) }; +} + +/// A [`PathBuf`] newtype that, when deserialized, automatically resolves +/// relative paths against the config file's directory. +/// +/// Use this type for any path field in a config struct that may be specified +/// relative to the config file. When the config is loaded via +/// [`load_toml_config`], [`CONFIG_BASE_DIR`] is set to the config file's parent +/// directory, and every `ConfigPath` field is resolved against it. +/// +/// Absolute paths pass through unchanged. Programmatically constructed +/// instances are never modified. +/// +/// # Example +/// +/// ```toml +/// [general] +/// pem = "wallets/owner.pem" # resolved relative to the config file location +/// ``` +#[derive(Debug, Clone)] +pub struct ConfigPath { + /// The raw path as written in the config file (relative). + pub relative_path: PathBuf, + /// The fully-resolved path, joined against the config file's directory. + /// Equal to `relative_path` for absolute paths or when no base dir is set. + pub absolute_path: PathBuf, +} + +impl ConfigPath { + /// Returns the resolved path (absolute when a base dir was available). + pub fn absolute_path(&self) -> &Path { + &self.absolute_path + } + + /// Returns the scenario expression for this path, rebased relative to `base`. + /// + /// The prefix is chosen based on the file extension: + /// - `mxsc:` for `.mxsc.json` contract bundles + /// - `file:` for all other paths + pub fn scenario_expr(&self, base: &Path) -> String { + let rebased = pathdiff::diff_paths(&self.absolute_path, base) + .unwrap_or_else(|| self.absolute_path.clone()); + let prefix = match self.absolute_path.to_str() { + Some(s) if s.ends_with(".mxsc.json") => "mxsc:", + _ => "file:", + }; + format!("{prefix}{}", rebased.display()) + } + + /// Sets the base directory used to resolve relative paths during + /// deserialization. Called by [`load_toml_config`] before and after + /// parsing; pass `None` to clear. + pub(crate) fn set_config_base_dir(base_dir: Option) { + CONFIG_BASE_DIR.with(|cell| *cell.borrow_mut() = base_dir); + } +} + +impl AsRef for ConfigPath { + fn as_ref(&self) -> &Path { + &self.absolute_path + } +} + +impl Deref for ConfigPath { + type Target = Path; + fn deref(&self) -> &Path { + &self.absolute_path + } +} + +impl fmt::Display for ConfigPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.absolute_path.display().fmt(f) + } +} + +impl From for ConfigPath { + fn from(p: PathBuf) -> Self { + ConfigPath { + relative_path: p.clone(), + absolute_path: p, + } + } +} + +impl From for PathBuf { + fn from(p: ConfigPath) -> Self { + p.absolute_path + } +} + +impl From<&str> for ConfigPath { + fn from(s: &str) -> Self { + ConfigPath::from(PathBuf::from(s)) + } +} + +impl<'de> Deserialize<'de> for ConfigPath { + fn deserialize>(deserializer: D) -> Result { + let raw = PathBuf::deserialize(deserializer)?; + if raw.is_absolute() { + return Ok(ConfigPath { + relative_path: raw.clone(), + absolute_path: raw, + }); + } + let resolved = CONFIG_BASE_DIR.with(|cell| { + cell.borrow() + .as_ref() + .map(|base| base.join(&raw)) + .unwrap_or_else(|| raw.clone()) + }); + Ok(ConfigPath { + relative_path: raw, + absolute_path: resolved, + }) + } +} + +/// Allows `ConfigPath` to be passed directly to `.code(...)` in interactor +/// transactions. The annotation uses the `mxsc:` prefix with the resolved +/// absolute path; the value is loaded from that file via the scenario +/// interpreter (which parses `.mxsc.json` and extracts the wasm bytecode). +impl AnnotatedValue> for &ConfigPath +where + Env: ScenarioTxEnv, +{ + fn annotation(&self, env: &Env) -> ManagedBuffer { + let context = env.env_data().interpreter_context(); + self.scenario_expr(&context.context_path).into() + } + + fn to_value(&self, env: &Env) -> ManagedBuffer { + let context = env.env_data().interpreter_context(); + let expr = self.scenario_expr(&context.context_path); + interpret_string(&expr, &context).into() + } +} + +impl AnnotatedValue> for ConfigPath +where + Env: ScenarioTxEnv, +{ + fn annotation(&self, env: &Env) -> ManagedBuffer { + (&self).annotation(env) + } + + fn to_value(&self, env: &Env) -> ManagedBuffer { + (&self).to_value(env) + } +} + +impl TxCodeValue for &ConfigPath where Env: ScenarioTxEnv {} +impl TxCodeValue for ConfigPath where Env: ScenarioTxEnv {} diff --git a/framework/snippets/src/config/connection_config.rs b/framework/snippets/src/config/connection_config.rs new file mode 100644 index 0000000000..335f9f4be5 --- /dev/null +++ b/framework/snippets/src/config/connection_config.rs @@ -0,0 +1,28 @@ +use serde::Deserialize; + +use super::ChainType; + +/// Configuration for connecting to the MultiversX network. +#[derive(Debug, Deserialize)] +pub struct ConnectionConfig { + pub gateway_uri: String, + pub chain_type: ChainType, +} + +impl ConnectionConfig { + /// Creates a default chain-simulator connection config pointing to localhost. + pub fn chain_simulator() -> Self { + ConnectionConfig { + gateway_uri: "http://localhost:8085".to_owned(), + chain_type: ChainType::Simulator, + } + } + + pub fn gateway_uri(&self) -> &str { + &self.gateway_uri + } + + pub fn use_chain_simulator(&self) -> bool { + matches!(self.chain_type, ChainType::Simulator) + } +} diff --git a/framework/snippets/src/config/interactor_config.rs b/framework/snippets/src/config/interactor_config.rs new file mode 100644 index 0000000000..bfcadcb6e5 --- /dev/null +++ b/framework/snippets/src/config/interactor_config.rs @@ -0,0 +1,63 @@ +use std::path::PathBuf; + +use crate::sdk::wallet::Wallet; + +use super::ConnectionConfig; + +/// Trait implemented by a contract-specific `Config` struct to provide initialization +/// details to the interactor. +/// +/// The `InteractorBase` calls `connection()` to configure the gateway and calls +/// `register_wallets()` to register every wallet that will sign transactions. +pub trait InteractorConfig { + /// Returns the connection settings (gateway URI, chain type). + fn connection(&self) -> &ConnectionConfig; + + /// Returns the wallets to be registered with the interactor during setup. + /// + /// Optional — defaults to no wallets. Additional wallets can always be + /// registered manually on the interactor after construction. + fn register_wallets(&self) -> Vec { + Vec::new() + } +} + +/// A bare [`ConnectionConfig`] can be used directly as an [`InteractorConfig`] +/// when no wallets or custom directory are needed. +impl InteractorConfig for ConnectionConfig { + fn connection(&self) -> &ConnectionConfig { + self + } +} + +/// Deserializes a TOML config file into a value of type `C`. +/// +/// Before parsing, sets the config file's parent directory as the base for +/// resolving relative [`WalletConfig`] paths (see [`WalletConfig::set_config_base_dir`]). +/// The base directory is cleared after parsing. +/// +/// # Panics +/// +/// Panics if the file cannot be opened, read, or parsed as valid TOML for `C`. +pub fn load_toml_config(config_path: &PathBuf) -> C +where + C: InteractorConfig + serde::de::DeserializeOwned, +{ + use super::config_path::ConfigPath; + use std::io::Read; + + let base_dir = config_path.parent().map(|p| p.to_path_buf()); + ConfigPath::set_config_base_dir(base_dir); + + let mut file = std::fs::File::open(config_path) + .unwrap_or_else(|e| panic!("cannot open {}: {e}", config_path.display())); + let mut content = String::new(); + file.read_to_string(&mut content) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", config_path.display())); + let result = toml::from_str(&content) + .unwrap_or_else(|e| panic!("cannot parse {}: {e}", config_path.display())); + + ConfigPath::set_config_base_dir(None); + + result +} diff --git a/framework/snippets/src/config/wallet_config.rs b/framework/snippets/src/config/wallet_config.rs new file mode 100644 index 0000000000..96ee12bd22 --- /dev/null +++ b/framework/snippets/src/config/wallet_config.rs @@ -0,0 +1,148 @@ +use multiversx_sc_scenario::imports::Bech32Address; +use serde::Deserialize; +use std::{path::PathBuf, sync::OnceLock}; + +use crate::sdk::{ + test_wallets, + wallet::{Keystore, Wallet}, +}; + +use super::ConfigPath; + +/// Raw helper used only for serde deserialization. +#[derive(Deserialize)] +struct WalletConfigRaw { + test_wallet: Option, + pem: Option, + keyfile: Option, + keystore_password: Option, + #[serde(default)] + ledger: bool, + #[serde(default)] + ledger_address_index: u32, +} + +/// Wallet configuration embeddable in a TOML/JSON config file. +/// Mirrors `SenderArgs` from the CLI but uses `serde` instead of `clap`. +#[derive(Debug, Deserialize)] +#[serde(from = "WalletConfigRaw")] +pub struct WalletConfig { + /// Name of a built-in test wallet (e.g. "alice", "bob", "mike"). + /// See `multiversx_sdk::test_wallets::valid_names()` for the full list. + pub test_wallet: Option, + + /// Path to a PEM wallet file. + pub pem: Option, + + /// Path to a JSON keystore wallet file. + pub keyfile: Option, + + /// Keystore password (plain text). Required when `keyfile` is set. + pub keystore_password: Option, + + /// Use the Ledger hardware device for signing. + pub ledger: bool, + + /// Address index to use on the Ledger device (default: 0). + pub ledger_address_index: u32, + + cache: OnceLock, +} + +impl From for WalletConfig { + fn from(raw: WalletConfigRaw) -> Self { + WalletConfig { + test_wallet: raw.test_wallet, + pem: raw.pem.map(Into::into), + keyfile: raw.keyfile.map(Into::into), + keystore_password: raw.keystore_password, + ledger: raw.ledger, + ledger_address_index: raw.ledger_address_index, + cache: OnceLock::new(), + } + } +} + +impl WalletConfig { + /// Creates a `WalletConfig` from a built-in test wallet name. + pub fn from_test_wallet(name: impl Into) -> Self { + WalletConfig { + test_wallet: Some(name.into()), + pem: None, + keyfile: None, + keystore_password: None, + ledger: false, + ledger_address_index: 0, + cache: OnceLock::new(), + } + } + + /// Creates a `WalletConfig` from a PEM file path. + pub fn from_pem(path: impl Into) -> Self { + WalletConfig { + test_wallet: None, + pem: Some(path.into()), + keyfile: None, + keystore_password: None, + ledger: false, + ledger_address_index: 0, + cache: OnceLock::new(), + } + } + + /// Returns the wallet, loading and caching it on first call. + /// + /// Priority: `test_wallet` > `pem` > `keyfile` > `ledger`. + /// Panics if none of the sources are set, or if loading fails. + pub fn wallet(&self) -> &Wallet { + self.cache.get_or_init(|| self.load_wallet()) + } + + /// Returns the on-chain address derived from this wallet config. + pub fn address(&self) -> Bech32Address { + self.wallet().to_bech32() + } + + fn load_wallet(&self) -> Wallet { + if let Some(name) = &self.test_wallet { + test_wallets::by_name(name.as_str()) + .unwrap_or_else(|| panic!("unknown test wallet name: '{name}'")) + } else if let Some(pem) = &self.pem { + Wallet::from_pem_file(pem).expect("failed to load PEM wallet") + } else if let Some(keyfile) = &self.keyfile { + let password = self + .keystore_password + .as_deref() + .expect("keystore_password is required when using keyfile"); + let keystore = Keystore::from_file(keyfile).expect("failed to load keystore file"); + keystore + .decrypt_wallet(password) + .expect("failed to decrypt wallet") + } else if self.ledger { + self.load_ledger_wallet() + } else { + panic!("WalletConfig requires one of: `test_wallet`, `pem`, `keyfile`, or `ledger`") + } + } + + #[cfg(feature = "ledger")] + fn load_ledger_wallet(&self) -> Wallet { + use crate::sdk::wallet::ledger::LedgerApp; + use multiversx_sdk::chain_core::std::Bech32Hrp; + + let mut app = LedgerApp::new().expect("failed to connect to Ledger device"); + let bech32_str = app + .get_address(self.ledger_address_index) + .expect("failed to get address from Ledger"); + let bech32_addr = + multiversx_sdk::chain_core::std::Bech32Address::from_bech32_string(bech32_str); + let hrp: Bech32Hrp = bech32_addr.hrp; + let address = bech32_addr.address; + Wallet::new_ledger(address, hrp, self.ledger_address_index) + } + + #[cfg(not(feature = "ledger"))] + fn load_ledger_wallet(&self) -> Wallet { + panic!("Ledger support is not compiled in; rebuild with the `ledger` feature enabled") + } +} diff --git a/framework/snippets/src/imports.rs b/framework/snippets/src/imports.rs index 2e531f793f..faaf47e41b 100644 --- a/framework/snippets/src/imports.rs +++ b/framework/snippets/src/imports.rs @@ -5,8 +5,9 @@ pub use crate::{ InteractorSimulateGasAsync, SimulateGas, StepBuffer, dns_address_for_name, }; +pub use crate::config::*; pub use crate::sdk::{ - data::keystore::InsertPassword, test_wallets, validator::Validator, wallet::Wallet, + gateway::SetStateAccount, test_wallets, validator::Validator, wallet::Wallet, }; pub use env_logger; diff --git a/framework/snippets/src/interactor.rs b/framework/snippets/src/interactor.rs index 3c58e13b17..424d0f0a4e 100644 --- a/framework/snippets/src/interactor.rs +++ b/framework/snippets/src/interactor.rs @@ -1,13 +1,16 @@ mod explorer_url; mod interactor_base; mod interactor_chain_simulator; +mod interactor_dispatch; mod interactor_dns; mod interactor_scenario; mod interactor_sender; mod interactor_tx; +mod tx_output_file; pub use explorer_url::ExplorerUrl; pub use interactor_base::*; pub use interactor_dns::*; pub use interactor_sender::*; pub use interactor_tx::*; +pub use tx_output_file::TxOutputFile; diff --git a/framework/snippets/src/interactor/interactor_base.rs b/framework/snippets/src/interactor/interactor_base.rs index 2cfeb24f90..333ae39f9a 100644 --- a/framework/snippets/src/interactor/interactor_base.rs +++ b/framework/snippets/src/interactor/interactor_base.rs @@ -5,7 +5,10 @@ use multiversx_sc_scenario::{ meta::tools::find_current_workspace, multiversx_sc::types::Address, }; -use multiversx_sdk::gateway::{GatewayAsyncService, NetworkConfigRequest, SetStateAccount}; +use multiversx_sdk::{ + chain_core::std::Bech32Hrp, + gateway::{GatewayAsyncService, NetworkConfigRequest, SetStateAccount}, +}; use super::ExplorerUrl; use std::{ @@ -16,73 +19,315 @@ use std::{ time::Duration, }; -use crate::{Sender, account_tool::retrieve_account_as_scenario_set_state}; +use crate::{ + Sender, + account_tool::retrieve_account_as_scenario_set_state, + config::{InteractorConfig, load_toml_config}, +}; +const DEFAULT_CONFIG_FILE_NAME: &str = "config.toml"; pub const INTERACTOR_SCENARIO_TRACE_PATH: &str = "interactor_trace.scen.json"; pub const INTERACTOR_SET_STATE_PATH: &str = "set_state.json"; +pub const CHAIN_SIMULATOR_CHAIN_ID: &str = "chain"; -pub struct InteractorBase +/// Holds the gateway connection and chain network configuration together. +/// +/// `proxy` and `network_config` are always obtained from the same gateway call, so +/// they are stored as a unit in [`InteractorBase::connection`] and are either both +/// present or both absent. +pub struct InteractorConnection where GatewayProxy: GatewayAsyncService, { + /// Gateway URI used to construct `proxy`. + pub gateway_uri: String, + /// The async gateway used to communicate with the blockchain network. pub proxy: GatewayProxy, - pub use_chain_simulator: bool, + /// Chain-level parameters (chain ID, gas price, HRP, …) fetched from the gateway. pub network_config: NetworkConfig, - pub sender_map: HashMap, +} + +/// Core interactor state shared across all contract-specific interactors. +/// +/// An `InteractorBase` is created in two phases: +/// +/// 1. **Construction** – call [`Self::empty`] (or [`Self::new`] for the +/// backwards-compatible path) to obtain an instance whose gateway fields are +/// still uninitialised. +/// 2. **Initialisation** – connect to a gateway via [`Self::with_connection`] or +/// load a full typed config via [`Self::load_config`] / +/// [`Self::load_config_toml`] / [`Self::load_config_from_file`]. +/// +/// Once initialised the interactor exposes helpers for sending transactions, +/// querying state, registering wallets, managing chain-simulator blocks, and +/// persisting/restoring account state to/from disk. +pub struct InteractorBase +where + GatewayProxy: GatewayAsyncService, +{ + /// Working directory used to resolve file paths such as `config.toml`, + /// `state.toml`, and the set-state snapshot file. + /// + /// Set at construction time via [`Self::with_current_dir`] or + /// [`Self::set_current_dir_from_workspace`]. + pub current_dir: PathBuf, + + /// When `true` the interactor is connected to the MultiversX Chain Simulator + /// and will issue simulator-specific requests (fund accounts, generate blocks, + /// set state, …). When `false` those requests are silently skipped. + pub use_chain_simulator: bool, + + /// Gateway proxy and network config, initialised together by [`Self::with_connection`]. + connection: Option>, + + /// Gas price used when building transactions, derived from + /// `connection.network_config.min_gas_price` at connection time. pub gas_price: u64, - pub waiting_time_ms: u64, + /// Explicit transaction nonce. When set, transaction preparation uses this + /// value instead of fetching the sender nonce from the gateway. + /// + /// Only applies for the next transaction; it is cleared after use. + /// + /// This is used to implement the `--nonce` CLI flag, + /// which overrides the nonce for a single transaction. + pub override_next_tx_nonce: Option, + + /// All wallets registered as transaction signers, keyed by their on-chain + /// address. Populated by [`Self::register_wallet`]. + pub sender_map: HashMap, + + /// Scenario runners executed **before** each step (e.g. pre-condition checks + /// or state loading). pub pre_runners: ScenarioRunnerList, + + /// Scenario runners executed **after** each step (e.g. trace file writers). pub post_runners: ScenarioRunnerList, - pub current_dir: PathBuf, + /// Optional block-explorer base URL derived from the network chain ID. Used + /// to pretty-print transaction and address links in log output. pub explorer_url: Option, + + /// Cumulative wall-clock time (in milliseconds) spent waiting inside + /// [`Self::sleep`] calls. + pub waiting_time_ms: u64, } impl InteractorBase where GatewayProxy: GatewayAsyncService, { - /// Not yet changed for backwards compatibility. + /// Creates an uninitialized interactor instance. + /// + /// Use [`Self::with_connection`] to initialize gateway-dependent fields. + pub fn empty() -> Self { + Self { + connection: None, + use_chain_simulator: false, + sender_map: HashMap::new(), + waiting_time_ms: 0, + pre_runners: ScenarioRunnerList::empty(), + post_runners: ScenarioRunnerList::empty(), + current_dir: PathBuf::default(), + gas_price: 0, + override_next_tx_nonce: None, + explorer_url: None, + } + } + + /// Backwards-compatible constructor that initializes the interactor connection. pub async fn new(gateway_uri: &str) -> Self { + Self::empty().with_connection(gateway_uri).await + } + + /// Initializes the interactor current directory. + pub fn with_current_dir>(mut self, current_dir: P) -> Self { + self.current_dir = current_dir.into(); + self + } + + /// Initializes gateway-specific fields from a gateway URI. + /// + /// This sets: + /// - `connection.proxy` + /// - `connection.network_config` + /// - `gas_price` + /// - `explorer_url` + async fn init_connection(&mut self, gateway_uri: &str) { let proxy = GatewayProxy::from_uri(gateway_uri); let network_config = proxy .request(NetworkConfigRequest) .await .expect("could not get network config"); - let gas_price = network_config.min_gas_price; - let explorer_url = ExplorerUrl::from_chain_id(&network_config.chain_id); - Self { + self.gas_price = network_config.min_gas_price; + self.explorer_url = ExplorerUrl::from_chain_id(&network_config.chain_id); + self.connection = Some(InteractorConnection { + gateway_uri: gateway_uri.to_owned(), proxy, - use_chain_simulator: false, network_config, - sender_map: HashMap::new(), - waiting_time_ms: 0, - pre_runners: ScenarioRunnerList::empty(), - post_runners: ScenarioRunnerList::empty(), - current_dir: PathBuf::default(), - gas_price, - explorer_url, + }); + } + + /// Ensures config-loading initialization runs only once. + /// + /// # Panics + /// + /// Panics if connection is already initialized. + fn assert_uninitialized_for_config_loading(&self) { + if self.connection.is_some() { + panic!( + "interactor connection already initialized; config loading must be called only on an uninitialized interactor" + ); + } + } + + /// Applies connection settings from a typed config. + /// + /// This initializes the gateway connection and chain-simulator toggle. + async fn apply_config_connection(&mut self, config: &C) + where + C: InteractorConfig, + { + let conn = config.connection(); + let gateway_uri = conn.gateway_uri().to_owned(); + self.init_connection(&gateway_uri).await; + self.use_chain_simulator = conn.use_chain_simulator(); + } + + /// Initializes connection-dependent fields on an interactor. + pub async fn with_connection(mut self, gateway_uri: &str) -> Self { + self.init_connection(gateway_uri).await; + self + } + + /// Loads an already-constructed config into this interactor. + /// + /// This initializes connection details, registers wallets from + /// [`InteractorConfig::register_wallets`], and advances chain-simulator blocks + /// until activation epoch is reached. + /// + /// # Panics + /// + /// Panics if the interactor is already initialized. + pub async fn load_config(&mut self, config: &C) + where + C: InteractorConfig, + { + self.assert_uninitialized_for_config_loading(); + self.apply_config_connection(config).await; + for wallet in config.register_wallets() { + self.register_wallet(wallet).await; + } + self.generate_blocks_until_all_activations().await; + } + + /// Builder-style variant of [`Self::load_config`]. + pub async fn with_config(mut self, config: &C) -> Self + where + C: InteractorConfig, + { + self.load_config(config).await; + self + } + + /// Loads `config.toml` from [`InteractorBase::current_dir`] and initializes the interactor. + pub async fn load_config_toml(&mut self) -> C + where + C: InteractorConfig + serde::de::DeserializeOwned, + { + self.load_config_from_file(DEFAULT_CONFIG_FILE_NAME).await + } + + /// Loads a config file relative to [`InteractorBase::current_dir`] and initializes the interactor. + /// + /// Returns the parsed config value so callers can keep their typed config. + pub async fn load_config_from_file(&mut self, config_file: P) -> C + where + C: InteractorConfig + serde::de::DeserializeOwned, + P: AsRef, + { + let config_path = self.current_dir.join(config_file.as_ref()); + let config: C = load_toml_config(&config_path); + self.load_config(&config).await; + config + } + + fn connection(&self) -> &InteractorConnection { + self.connection.as_ref().expect( + "interactor connection is uninitialized; call InteractorBase::with_connection(...) or InteractorBase::new(...) first", + ) + } + + /// Returns the initialized gateway proxy reference. + /// + /// # Panics + /// + /// Panics if connection has not been initialized. + pub fn proxy(&self) -> &GatewayProxy { + &self.connection().proxy + } + + /// Returns the gateway URI used to initialize the connection. + /// + /// # Panics + /// + /// Panics if connection has not been initialized. + pub fn gateway_uri(&self) -> &str { + &self.connection().gateway_uri + } + + /// Returns the initialized network configuration. + /// + /// # Panics + /// + /// Panics if connection has not been initialized. + pub fn network_config(&self) -> &NetworkConfig { + &self.connection().network_config + } + + /// Validates that `chain_id` matches the chain ID reported by the gateway. + pub fn validate_chain_id(&self, chain_id: &str) -> anyhow::Result<()> { + let network_chain_id = &self.network_config().chain_id; + if chain_id != network_chain_id { + anyhow::bail!( + "chain ID mismatch: CLI specifies {chain_id}, but gateway reports {network_chain_id}" + ); } + Ok(()) } + /// Enables or disables chain-simulator mode. pub fn use_chain_simulator(mut self, use_chain_simulator: bool) -> Self { self.use_chain_simulator = use_chain_simulator; self } + /// Enables or disables chain-simulator mode automatically, based on the chain ID + /// reported by the gateway: the chain simulator reports chain ID [`CHAIN_SIMULATOR_CHAIN_ID`]. + /// + /// # Panics + /// + /// Panics if connection has not been initialized. + pub fn use_chain_simulator_auto(mut self) -> Self { + self.use_chain_simulator = self.network_config().chain_id == CHAIN_SIMULATOR_CHAIN_ID; + self + } + + /// Registers a wallet as a transaction sender and funds it in simulator mode. + /// + /// Returns the wallet address. pub async fn register_wallet(&mut self, wallet: Wallet) -> Address { let address = wallet.to_address(); self.send_user_funds(&address.to_bech32(self.get_hrp())) .await .unwrap(); - self.generate_blocks(1).await.unwrap(); + self.generate_blocks(10).await.unwrap(); self.sender_map.insert( address.clone(), Sender { address: address.clone(), - hrp: self.network_config.address_hrp.clone(), + hrp: self.get_hrp(), wallet, current_nonce: None, }, @@ -90,20 +335,48 @@ where address } + /// Registers a wallet as a transaction sender and funds it in simulator mode. + /// + /// Returns the wallet address encoded as bech32 using this interactor's + /// configured network HRP. + pub async fn register_wallet_bech32(&mut self, wallet: Wallet) -> Bech32Address { + self.register_wallet(wallet).await.to_bech32(self.get_hrp()) + } + + /// Registers an optional wallet as a transaction sender and funds it in + /// simulator mode. + /// + /// Returns `Some` containing the wallet address encoded as bech32 using + /// this interactor's configured network HRP. Returns `None` when no wallet + /// is provided. + pub async fn register_wallet_bech32_opt( + &mut self, + wallet: Option, + ) -> Option { + if let Some(w) = wallet { + Some(self.register_wallet_bech32(w).await) + } else { + None + } + } + + /// Sleeps for `duration` using the configured gateway and accumulates waited time. pub async fn sleep(&mut self, duration: Duration) { let millis = duration.as_millis() as u64; self.waiting_time_ms += millis; - self.proxy.sleep(millis).await; + self.proxy().sleep(millis).await; } + /// Adds a scenario trace output runner to this interactor. pub async fn with_tracer>(mut self, path: P) -> Self { self.post_runners.push(ScenarioTraceFile::new(path)); self } + /// Fetches on-chain account state and appends it to the local set-state file. pub async fn retrieve_account(&mut self, wallet_address: &Bech32Address) { let (set_state_account, set_state_step) = - retrieve_account_as_scenario_set_state(&self.proxy, wallet_address).await; + retrieve_account_as_scenario_set_state(self.proxy(), wallet_address).await; self.pre_runners.run_set_state_step(&set_state_step); self.post_runners.run_set_state_step(&set_state_step); @@ -111,18 +384,24 @@ where set_state_account.add_to_state_file(path.as_path()); } + /// Returns the absolute path to the persisted set-state file. pub fn get_state_file_path(&self) -> PathBuf { self.current_dir.join(INTERACTOR_SET_STATE_PATH) } - pub fn get_hrp(&self) -> &str { - &self.network_config.address_hrp + /// Returns the configured Bech32 HRP for this network. + pub fn get_hrp(&self) -> Bech32Hrp { + self.network_config().address_hrp } + /// Returns `true` if `address` belongs to a registered sender wallet. pub fn is_registered_wallet(&self, address: &Address) -> bool { self.sender_map.contains_key(address) } + /// Reads and parses all saved set-state accounts from disk. + /// + /// Returns an empty list if the file does not exist or cannot be parsed. pub fn get_accounts_from_file(&self) -> Vec { let file_path = self.get_state_file_path(); @@ -147,4 +426,25 @@ where self.current_dir = path; self } + + /// Loads `State` from `state.toml` in `current_dir` (or returns the default) + /// and wraps it in an [`AutoSave`] that persists changes on drop. + /// + /// Call [`AutoSave::disable`] or use [`AutoSave::no_save`] directly when you + /// do not want side-effects (e.g. in tests). + pub fn load_state(&self) -> crate::AutoSave + where + State: serde::Serialize + serde::de::DeserializeOwned + Default, + { + let path = self.current_dir.join("state.toml"); + let value = if path.exists() { + let content = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())); + toml::from_str(&content) + .unwrap_or_else(|e| panic!("cannot parse {}: {e}", path.display())) + } else { + State::default() + }; + crate::AutoSave::new(value, path) + } } diff --git a/framework/snippets/src/interactor/interactor_chain_simulator.rs b/framework/snippets/src/interactor/interactor_chain_simulator.rs index 27ebddd164..c785cc6158 100644 --- a/framework/snippets/src/interactor/interactor_chain_simulator.rs +++ b/framework/snippets/src/interactor/interactor_chain_simulator.rs @@ -14,12 +14,13 @@ impl InteractorBase where GatewayProxy: GatewayAsyncService, { + /// Send funds to a user account on the chain simulator. The amount is fixed to 100 EGLD. pub async fn send_user_funds(&self, receiver: &Bech32Address) -> Result { if !self.use_chain_simulator { return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorSendFundsRequest::to_address(receiver)) .await } @@ -29,7 +30,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorGenerateBlocksRequest::num_blocks(num_blocks)) .await } @@ -39,7 +40,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorAddKeysRequest::with_keys(vec![key])) .await } @@ -49,7 +50,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorGenerateBlocksRequest::until_epoch( epoch_number, )) @@ -69,7 +70,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorGenerateBlocksRequest::until_tx_processed( tx_hash, )) @@ -81,7 +82,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorSetStateRequest::for_accounts(accounts)) .await } @@ -94,7 +95,7 @@ where return Ok(String::from("no-simulator")); } - self.proxy + self.proxy() .request(ChainSimulatorSetStateOverwriteRequest::for_accounts( accounts, )) @@ -107,7 +108,7 @@ where } let accounts = self.get_accounts_from_file(); - self.proxy + self.proxy() .request(ChainSimulatorSetStateRequest::for_accounts(accounts)) .await } diff --git a/framework/snippets/src/interactor/interactor_dispatch.rs b/framework/snippets/src/interactor/interactor_dispatch.rs new file mode 100644 index 0000000000..685bf33cba --- /dev/null +++ b/framework/snippets/src/interactor/interactor_dispatch.rs @@ -0,0 +1,138 @@ +use crate::{InteractorBase, sdk::data::transaction::Transaction}; +use anyhow::Context; +use multiversx_sdk::gateway::GatewayAsyncService; +use serde::Serialize; + +use super::{ExplorerUrl, TxOutputFile}; +use std::path::Path; + +impl InteractorBase +where + GatewayProxy: GatewayAsyncService, +{ + /// Serialize a value to JSON with 4-space indentation (matching mxpy output). + pub fn to_json_pretty(value: &T) -> anyhow::Result { + to_json_pretty(value) + } + + fn print_tx_url(tx_hash: &str, chain_id: &str, info: &str) { + if let Some(explorer_url) = ExplorerUrl::from_chain_id(chain_id) { + println!("{info}: {}", explorer_url.tx_url(tx_hash)); + } else { + println!("{info} tx hash: {tx_hash}"); + } + } + + /// Broadcast `transaction` through the configured gateway. + /// + /// Returns the transaction hash reported by the gateway. On success, logs the hash and + /// prints its explorer URL when `transaction.chain_id` is supported; otherwise prints the + /// hash. On failure, logs and prints the gateway error before returning it with `info` as + /// context. + pub async fn broadcast_transaction( + &self, + transaction: &Transaction, + info: &str, + ) -> anyhow::Result { + let result = self + .proxy() + .request(multiversx_sdk::gateway::SendTxRequest(transaction)) + .await; + match result { + Ok(tx_hash) => { + log::info!("{info} tx hash: {tx_hash}"); + Self::print_tx_url(&tx_hash, &transaction.chain_id, info); + Ok(tx_hash) + } + Err(err) => { + let error = anyhow::anyhow!("{info} error: {err}"); + eprintln!("{error}"); + log::error!("{error}"); + Err(error) + } + } + } + + /// Broadcast the transaction inside `output`, update the hash (and optionally + /// the on-network result), then write/print the updated output. + pub async fn broadcast_and_save( + &self, + output: TxOutputFile, + outfile: Option<&Path>, + wait_result: bool, + ) -> anyhow::Result<()> { + if output.emitted_transaction.signature.is_none() { + anyhow::bail!("transaction is not signed; sign it before broadcasting"); + } + if output.emitted_transaction.relayer.is_some() + && output.emitted_transaction.relayer_signature.is_none() + { + anyhow::bail!( + "relayed transaction is missing relayer signature; use `tx relay` to add it" + ); + } + + let tx_hash = self + .broadcast_transaction(&output.emitted_transaction, "transaction") + .await?; + + let mut output_with_hash = TxOutputFile { + emitted_transaction_hash: tx_hash.clone(), + ..output + }; + + if wait_result { + println!("Waiting for transaction result..."); + + self.generate_blocks_until_tx_processed(&tx_hash).await?; + + let (tx_on_network, return_code) = + multiversx_sdk::retrieve_tx_on_network(self.proxy(), tx_hash).await?; + let tx_response = + crate::network_response::parse_tx_response(tx_on_network.clone(), return_code); + print_tx_results(&tx_response); + output_with_hash.transaction_on_network = Some(tx_on_network); + } + + output_with_hash.save_output(outfile) + } + + /// Sign `transaction`, then write / print / broadcast it according to the supplied flags. + pub async fn sign_and_dispatch( + &self, + mut transaction: Transaction, + send: bool, + wait_result: bool, + outfile: Option<&Path>, + ) -> anyhow::Result<()> { + self.sign_tx(&mut transaction); + let output = TxOutputFile::from_transaction(transaction)?; + + if send { + self.broadcast_and_save(output, outfile, wait_result).await + } else { + output.save_output(outfile) + } + } +} + +fn to_json_pretty(value: &T) -> anyhow::Result { + let mut buf = Vec::new(); + let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter); + value + .serialize(&mut serializer) + .context("failed to serialize transaction")?; + String::from_utf8(buf).context("non-UTF8 in serialized JSON") +} + +fn print_tx_results(tx_response: &multiversx_sc_scenario::scenario_model::TxResponse) { + if tx_response.tx_error.is_success() { + println!("Transaction successful."); + } else { + println!("Transaction failed: {}", tx_response.tx_error); + } + for (index, result) in tx_response.out.iter().enumerate() { + println!("Result[{index}]: 0x{}", hex::encode(result)); + } +} diff --git a/framework/snippets/src/interactor/interactor_scenario/error_message.rs b/framework/snippets/src/interactor/interactor_scenario/error_message.rs index 50211b29b1..4eb3c73d1f 100644 --- a/framework/snippets/src/interactor/interactor_scenario/error_message.rs +++ b/framework/snippets/src/interactor/interactor_scenario/error_message.rs @@ -8,14 +8,6 @@ pub(super) fn query_err_message(err: &anyhow::Error) { ); } -pub(super) fn transfer_err_message(err: &anyhow::Error) { - eprintln!( - "{}{}", - "Transfer failed: ".to_string().red().bold(), - err.to_string().red().bold() - ); -} - pub(super) fn simulate_gas_transfer_err_message(err: &anyhow::Error) { eprintln!( "{}{}", @@ -27,14 +19,6 @@ pub(super) fn simulate_gas_transfer_err_message(err: &anyhow::Error) { ); } -pub(super) fn deploy_err_message(err: &anyhow::Error) { - eprintln!( - "{}{}", - "Deploy failed: ".to_string().red().bold(), - err.to_string().red().bold() - ); -} - pub(super) fn simulate_gas_deploy_err_message(err: &anyhow::Error) { eprintln!( "{}{}", @@ -46,14 +30,6 @@ pub(super) fn simulate_gas_deploy_err_message(err: &anyhow::Error) { ); } -pub(crate) fn sc_call_err_message(err: &anyhow::Error) { - eprintln!( - "{}{}", - "Call failed: ".to_string().red().bold(), - err.to_string().red().bold() - ); -} - pub(crate) fn simulate_gas_sc_call_err_message(err: &anyhow::Error) { eprintln!( "{}{}", diff --git a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs index 4b6d2758b2..0ebdcd4603 100644 --- a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs +++ b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_call.rs @@ -1,19 +1,15 @@ use std::process; -use super::error_message::{sc_call_err_message, simulate_gas_sc_call_err_message}; +use super::error_message::simulate_gas_sc_call_err_message; use crate::{InteractorBase, SimulateGas, network_response}; use multiversx_sc_scenario::{ - imports::Bech32Address, scenario::ScenarioRunner, scenario_model::{ScCallStep, SetStateStep, TxCall}, }; -use multiversx_sdk::{ - data::transaction::Transaction, gateway::SimulateTxRequest, utils::base64_encode, -}; -use multiversx_sdk::{ - gateway::{GatewayAsyncService, SendTxRequest}, - retrieve_tx_on_network, -}; +use multiversx_sdk::chain_core::std::base64_encode; +use multiversx_sdk::data::transaction::{TransactionOptions, TransactionVersion}; +use multiversx_sdk::{data::transaction::Transaction, gateway::SimulateTxRequest}; +use multiversx_sdk::{gateway::GatewayAsyncService, retrieve_tx_on_network}; impl InteractorBase where @@ -29,18 +25,19 @@ where transaction.gas_limit = gas; // sign again, because gas changed - let sender_address = &sc_call_step.tx.from.value; - self.sign_tx(sender_address, &mut transaction); + self.sign_tx(&mut transaction); } self.pre_runners.run_sc_call_step(sc_call_step); - let tx_hash = self.launch_sc_call(&transaction).await; + let Ok(tx_hash) = self.broadcast_transaction(&transaction, "sc call").await else { + process::exit(1) + }; self.generate_blocks_until_tx_processed(&tx_hash) .await .unwrap(); - let (tx, return_code) = retrieve_tx_on_network(&self.proxy, tx_hash) + let (tx, return_code) = retrieve_tx_on_network(self.proxy(), tx_hash) .await .expect("failed to fetch transaction result"); @@ -64,39 +61,16 @@ where } async fn tx_call_to_blockchain_signed_tx(&mut self, sc_call_step: &ScCallStep) -> Transaction { - let sender_address = &sc_call_step.tx.from.value; let mut transaction = self.tx_call_to_blockchain_tx(&sc_call_step.tx); - self.set_tx_nonce_update_sender(sender_address, &mut transaction) - .await; - self.sign_tx(sender_address, &mut transaction); + // Set relayer field before signing — it is included in the signing bytes. + self.set_tx_nonce_update_sender(&mut transaction).await; + self.sign_tx(&mut transaction); transaction } - async fn launch_sc_call(&mut self, transaction: &Transaction) -> String { - let tx_hash = self.proxy.request(SendTxRequest(transaction)).await; - - match tx_hash { - Ok(tx_hash) => { - log::info!("sc call tx hash: {tx_hash}"); - if let Some(ex) = &self.explorer_url { - println!("sc call: {}", ex.tx_url(&tx_hash)); - } else { - println!("sc call tx hash: {tx_hash}"); - } - tx_hash - } - Err(err) => { - println!("sc call error: {err}"); - log::error!("sc call error: {err}"); - sc_call_err_message(&err); - process::exit(1) - } - } - } - async fn sc_call_simulate_transaction(&mut self, transaction: &Transaction) -> u64 { - let result = self.proxy.request(SimulateTxRequest(transaction)).await; + let result = self.proxy().request(SimulateTxRequest(transaction)).await; match result { Ok(gas) => { @@ -126,15 +100,20 @@ where Transaction { nonce: 0, value: normalized.egld_value.value.to_string(), - sender: Bech32Address::encode_address(hrp, normalized.from.to_address()), - receiver: Bech32Address::encode_address(hrp, normalized.to.to_address()), + sender: normalized.from.to_address().to_bech32(hrp), + receiver: normalized.to.to_address().to_bech32(hrp), gas_price: self.gas_price, gas_limit: normalized.gas_limit.value, data, signature: None, - chain_id: self.network_config.chain_id.clone(), - version: self.network_config.min_transaction_version, - options: 0, + chain_id: self.network_config().chain_id.clone(), + version: TransactionVersion::V2, + options: Some(TransactionOptions::SIGN_WITH_HASH), + relayer: normalized + .relayer + .as_ref() + .map(|a| a.to_address().to_bech32(hrp)), + relayer_signature: None, } } } diff --git a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_deploy.rs b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_deploy.rs index de93bcaf01..7824c1db36 100644 --- a/framework/snippets/src/interactor/interactor_scenario/interactor_sc_deploy.rs +++ b/framework/snippets/src/interactor/interactor_scenario/interactor_sc_deploy.rs @@ -1,39 +1,42 @@ use std::process; -use super::error_message::{deploy_err_message, simulate_gas_deploy_err_message}; +use super::error_message::simulate_gas_deploy_err_message; use crate::{InteractorBase, SimulateGas, network_response}; use multiversx_sc_scenario::{ imports::Bech32Address, mandos_system::ScenarioRunner, scenario_model::{ScDeployStep, SetStateStep}, }; -use multiversx_sdk::{ - data::transaction::Transaction, gateway::SimulateTxRequest, utils::base64_encode, -}; -use multiversx_sdk::{ - gateway::{GatewayAsyncService, SendTxRequest}, - retrieve_tx_on_network, -}; +use multiversx_sdk::chain_core::std::base64_encode; +use multiversx_sdk::data::transaction::{TransactionOptions, TransactionVersion}; +use multiversx_sdk::{data::transaction::Transaction, gateway::SimulateTxRequest}; +use multiversx_sdk::{gateway::GatewayAsyncService, retrieve_tx_on_network}; impl InteractorBase where GatewayProxy: GatewayAsyncService, { pub(crate) fn sc_deploy_to_blockchain_tx(&self, sc_deploy_step: &ScDeployStep) -> Transaction { - let hrp = self.network_config.address_hrp.clone(); + let hrp = self.network_config().address_hrp; Transaction { nonce: 0, value: sc_deploy_step.tx.egld_value.value.to_string(), - sender: sc_deploy_step.tx.from.to_address().to_bech32(&hrp), - receiver: Bech32Address::zero(&hrp), + sender: sc_deploy_step.tx.from.to_address().to_bech32(hrp), + receiver: Bech32Address::zero(hrp), gas_price: self.gas_price, gas_limit: sc_deploy_step.tx.gas_limit.value, data: Some(base64_encode(sc_deploy_step.tx.to_tx_data())), signature: None, - chain_id: self.network_config.chain_id.clone(), - version: self.network_config.min_transaction_version, - options: 0, + chain_id: self.network_config().chain_id.clone(), + version: TransactionVersion::V2, + options: Some(TransactionOptions::SIGN_WITH_HASH), + relayer: sc_deploy_step + .tx + .relayer + .as_ref() + .map(|a| a.to_address().to_bech32(hrp)), + relayer_signature: None, } } @@ -41,37 +44,14 @@ where &mut self, sc_deploy_step: &ScDeployStep, ) -> Transaction { - let sender_address = &sc_deploy_step.tx.from.value; let mut transaction = self.sc_deploy_to_blockchain_tx(sc_deploy_step); - self.set_tx_nonce_update_sender(sender_address, &mut transaction) - .await; - self.sign_tx(sender_address, &mut transaction); + // Set relayer field before signing — it is included in the signing bytes. + self.set_tx_nonce_update_sender(&mut transaction).await; + self.sign_tx(&mut transaction); transaction } - async fn launch_sc_deploy(&mut self, transaction: &Transaction) -> String { - let tx_hash_result = self.proxy.request(SendTxRequest(transaction)).await; - - match tx_hash_result { - Ok(tx_hash) => { - log::info!("sc deploy tx hash: {tx_hash}"); - if let Some(ex) = &self.explorer_url { - println!("sc deploy: {}", ex.tx_url(&tx_hash)); - } else { - println!("sc deploy tx hash: {tx_hash}"); - } - tx_hash - } - Err(err) => { - println!("sc deploy error: {err}"); - log::error!("sc deploy error: {err}"); - deploy_err_message(&err); - process::exit(1) - } - } - } - pub async fn sc_deploy(&mut self, sc_deploy_step: &mut ScDeployStep) { let mut transaction = self.sc_deploy_to_blockchain_signed_tx(sc_deploy_step).await; @@ -82,18 +62,19 @@ where transaction.gas_limit = gas; // sign again, because gas changed - let sender_address = &sc_deploy_step.tx.from.value; - self.sign_tx(sender_address, &mut transaction); + self.sign_tx(&mut transaction); } self.pre_runners.run_sc_deploy_step(sc_deploy_step); - let tx_hash = self.launch_sc_deploy(&transaction).await; + let Ok(tx_hash) = self.broadcast_transaction(&transaction, "sc deploy").await else { + process::exit(1) + }; self.generate_blocks_until_tx_processed(&tx_hash) .await .unwrap(); - let (tx, return_code) = retrieve_tx_on_network(&self.proxy, tx_hash.clone()) + let (tx, return_code) = retrieve_tx_on_network(self.proxy(), tx_hash.clone()) .await .expect("failed to fetch transaction result"); @@ -122,7 +103,7 @@ where } async fn sc_deploy_simulate_transaction(&mut self, transaction: &Transaction) -> u64 { - let gas_result = self.proxy.request(SimulateTxRequest(transaction)).await; + let gas_result = self.proxy().request(SimulateTxRequest(transaction)).await; match gas_result { Ok(gas) => { diff --git a/framework/snippets/src/interactor/interactor_scenario/interactor_transfer.rs b/framework/snippets/src/interactor/interactor_scenario/interactor_transfer.rs index 159362529a..994c06211a 100644 --- a/framework/snippets/src/interactor/interactor_scenario/interactor_transfer.rs +++ b/framework/snippets/src/interactor/interactor_scenario/interactor_transfer.rs @@ -1,12 +1,11 @@ use std::process; -use super::error_message::{simulate_gas_transfer_err_message, transfer_err_message}; +use super::error_message::simulate_gas_transfer_err_message; use crate::InteractorBase; -use log::info; use multiversx_sc_scenario::{scenario::ScenarioRunner, scenario_model::TransferStep}; use multiversx_sdk::{ data::transaction::Transaction, - gateway::{GatewayAsyncService, SendTxRequest, SimulateTxRequest}, + gateway::{GatewayAsyncService, SimulateTxRequest}, retrieve_tx_on_network, }; @@ -17,21 +16,14 @@ where pub async fn transfer(&mut self, transfer_step: TransferStep) -> String { let transaction = self.launch_transfer(&transfer_step).await; - let tx_hash = match self.proxy.request(SendTxRequest(&transaction)).await { - Ok(hash) => hash, - Err(err) => { - transfer_err_message(&err); - process::exit(1); - } + let Ok(tx_hash) = self.broadcast_transaction(&transaction, "transfer").await else { + process::exit(1) }; self.generate_blocks_until_tx_processed(&tx_hash) .await .unwrap(); - println!("transfer tx hash: {tx_hash}"); - info!("transfer tx hash: {}", tx_hash); - - retrieve_tx_on_network(&self.proxy, tx_hash.clone()) + retrieve_tx_on_network(self.proxy(), tx_hash.clone()) .await .expect("failed to fetch transfer tx result"); @@ -43,7 +35,7 @@ where pub async fn simulate_gas_transfer(&mut self, transfer_step: TransferStep) -> u64 { let transaction = self.launch_transfer(&transfer_step).await; - match self.proxy.request(SimulateTxRequest(&transaction)).await { + match self.proxy().request(SimulateTxRequest(&transaction)).await { Ok(gas) => { println!("Gas simulation for the SC transfer: {gas} units."); log::info!("Gas simulation for the SC transfer: {gas} units."); @@ -58,12 +50,9 @@ where async fn launch_transfer(&mut self, transfer_step: &TransferStep) -> Transaction { self.pre_runners.run_transfer_step(transfer_step); - - let sender_address = &transfer_step.tx.from.value; let mut transaction = self.tx_call_to_blockchain_tx(&transfer_step.tx.to_tx_call()); - self.set_tx_nonce_update_sender(sender_address, &mut transaction) - .await; - self.sign_tx(sender_address, &mut transaction); + self.set_tx_nonce_update_sender(&mut transaction).await; + self.sign_tx(&mut transaction); transaction } diff --git a/framework/snippets/src/interactor/interactor_scenario/interactor_vm_query.rs b/framework/snippets/src/interactor/interactor_scenario/interactor_vm_query.rs index 83684de909..b1883f165f 100644 --- a/framework/snippets/src/interactor/interactor_scenario/interactor_vm_query.rs +++ b/framework/snippets/src/interactor/interactor_scenario/interactor_vm_query.rs @@ -17,9 +17,9 @@ where GatewayProxy: GatewayAsyncService, { pub async fn perform_sc_query(&mut self, step: &mut ScQueryStep) { - let hrp = self.network_config.address_hrp.clone(); + let hrp = self.network_config().address_hrp; - let sc_address = step.tx.to.to_address().to_bech32(&hrp); + let sc_address = step.tx.to.to_address().to_bech32(hrp); let req = VMQueryInput { sc_address, func_name: step.tx.function.clone(), @@ -30,7 +30,7 @@ where .map(|arg| hex::encode(&arg.value)) .collect(), }; - let result = match self.proxy.request(VMQueryRequest(&req)).await { + let result = match self.proxy().request(VMQueryRequest(&req)).await { Ok(r) => r, Err(err) => { query_err_message(&err); diff --git a/framework/snippets/src/interactor/interactor_sender.rs b/framework/snippets/src/interactor/interactor_sender.rs index 7f6265a0e8..bc8788fa30 100644 --- a/framework/snippets/src/interactor/interactor_sender.rs +++ b/framework/snippets/src/interactor/interactor_sender.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::sdk::{data::transaction::Transaction, wallet::Wallet}; use multiversx_sc_scenario::{imports::Bech32Address, multiversx_sc::types::Address}; +use multiversx_sdk::chain_core::std::Bech32Hrp; use multiversx_sdk::data::account::Account; use multiversx_sdk::data::esdt::EsdtBalance; use multiversx_sdk::gateway::{ @@ -13,7 +14,7 @@ use crate::InteractorBase; /// A user account that can sign transactions (a pem is present). pub struct Sender { pub address: Address, - pub hrp: String, + pub hrp: Bech32Hrp, pub wallet: Wallet, pub current_nonce: Option, } @@ -24,7 +25,7 @@ where { pub async fn recall_nonce(&self, address: &Address) -> u64 { let account = self - .proxy + .proxy() .request(GetAccountRequest::new(&address.to_bech32(self.get_hrp()))) .await .expect("failed to retrieve account nonce"); @@ -33,14 +34,14 @@ where } pub async fn get_account(&self, address: &Address) -> Account { - self.proxy + self.proxy() .request(GetAccountRequest::new(&address.to_bech32(self.get_hrp()))) .await .expect("failed to retrieve account") } pub async fn get_account_storage(&self, address: &Address) -> HashMap { - self.proxy + self.proxy() .request(GetAccountStorageRequest::new( &address.to_bech32(self.get_hrp()), )) @@ -62,7 +63,7 @@ where } pub async fn get_account_esdt(&self, address: &Address) -> HashMap { - self.proxy + self.proxy() .request(GetAccountEsdtTokensRequest::new( &address.to_bech32(self.get_hrp()), )) @@ -73,20 +74,12 @@ where /// Updates: /// - the transaction with the nonce read from the network /// - the sender's current_nonce - pub(crate) async fn set_tx_nonce_update_sender( - &mut self, - sender_address: &Address, - transaction: &mut Transaction, - ) { - // read - let sender = self - .sender_map - .get(sender_address) - .expect("the wallet that was supposed to sign is not registered"); + /// + /// Assumes that the transaction sender has already been set. + pub async fn set_tx_nonce_update_sender(&mut self, transaction: &mut Transaction) { + let sender_address = transaction.sender.as_address(); - // recall - let nonce = self.recall_nonce(&sender.address).await; - println!("sender's recalled nonce: {nonce}"); + let nonce = self.resolve_tx_nonce(sender_address).await; // set tx nonce transaction.nonce = nonce; @@ -100,15 +93,100 @@ where sender.current_nonce = Some(nonce + 1); } - pub(crate) fn sign_tx(&self, sender_address: &Address, transaction: &mut Transaction) { + pub(crate) async fn resolve_tx_nonce(&mut self, sender_address: &Address) -> u64 { + if let Some(nonce) = self.override_next_tx_nonce.take() { + println!("sender's overridden nonce: {nonce}"); + nonce + } else { + let nonce = self.recall_nonce(sender_address).await; + println!("sender's recalled nonce: {nonce}"); + nonce + } + } + + /// Signs the transaction with the sender's wallet and, if present, the relayer's wallet. + /// + /// Updates the transaction's `signature` field with the sender's signature and the + /// `relayer_signature` field if its wallet is registered. If only a relayer + /// address is present, the transaction is left without a relayer signature. + /// + /// # Panics + /// + /// Panics if the sender's wallet is not registered. + pub fn sign_tx(&self, transaction: &mut Transaction) { + self.sign_tx_for_sender(transaction); + self.sign_tx_for_relayer(transaction); + } + + fn sign_tx_for_sender(&self, transaction: &mut Transaction) { // read let sender = self .sender_map - .get(sender_address) + .get(transaction.sender.as_address()) .expect("the wallet that was supposed to sign is not registered"); // sign - let signature = sender.wallet.sign_tx(transaction); - transaction.signature = Some(hex::encode(signature)); + let signature = sender + .wallet + .sign_tx(transaction) + .expect("failed to sign transaction"); + transaction.signature = Some(signature); + } + + /// Signs the transaction as the relayer (adds `relayer_signature`). + fn sign_tx_for_relayer(&self, transaction: &mut Transaction) { + let Some(relayer_address) = &transaction.relayer else { + return; + }; + let Some(relayer) = self.sender_map.get(relayer_address.as_address()) else { + return; + }; + let sig = relayer + .wallet + .sign_tx(transaction) + .expect("failed to sign as relayer"); + transaction.relayer_signature = Some(sig); + } +} + +#[cfg(test)] +mod tests { + use multiversx_sc_scenario::{imports::Bech32Address, multiversx_sc::types::Address}; + use multiversx_sdk::data::transaction::{Transaction, TransactionVersion}; + + use crate::Interactor; + + #[tokio::test] + async fn nonce_override_does_not_require_a_gateway_connection() { + let mut interactor = Interactor::empty(); + interactor.override_next_tx_nonce = Some(42); + + assert_eq!(interactor.resolve_tx_nonce(&Address::zero()).await, 42); + assert_eq!(interactor.override_next_tx_nonce, None); + } + + #[test] + fn leaves_relayer_signature_empty_when_wallet_is_not_registered() { + let interactor = Interactor::empty(); + let address = Bech32Address::zero_default_hrp(); + let mut transaction = Transaction { + nonce: 0, + value: "0".to_owned(), + receiver: address.clone(), + sender: address.clone(), + gas_price: 1_000_000_000, + gas_limit: 100_000, + data: None, + signature: None, + chain_id: "D".to_owned(), + version: TransactionVersion::V2, + options: None, + relayer: Some(address), + relayer_signature: None, + }; + + interactor.sign_tx_for_relayer(&mut transaction); + + assert!(transaction.relayer_signature.is_none()); } } diff --git a/framework/snippets/src/interactor/interactor_tx/interactor_exec_env.rs b/framework/snippets/src/interactor/interactor_tx/interactor_exec_env.rs index c1b742cda8..3799818dc2 100644 --- a/framework/snippets/src/interactor/interactor_tx/interactor_exec_env.rs +++ b/framework/snippets/src/interactor/interactor_tx/interactor_exec_env.rs @@ -3,7 +3,8 @@ use multiversx_sc_scenario::{ api::StaticApi, imports::TxId, multiversx_sc::types::{ - H256, ManagedAddress, ManagedBuffer, Tx, TxBaseWithEnv, TxEnv, TxEnvWithTxHash, + H256, ManagedAddress, ManagedBuffer, Tx, TxBaseWithEnv, TxEnv, TxEnvWithRelayer, + TxEnvWithTxHash, }, scenario_model::TxExpect, }; @@ -61,6 +62,19 @@ where } } +impl TxEnvWithRelayer for InteractorEnvExec<'_, GatewayProxy> +where + GatewayProxy: GatewayAsyncService, +{ + fn set_relayer_address(&mut self, relayer: ManagedAddress) { + self.data.set_relayer_address(relayer); + } + + fn take_relayer_address(&mut self) -> Option> { + self.data.take_relayer_address() + } +} + impl TxEnvWithTxHash for InteractorEnvExec<'_, GatewayProxy> where GatewayProxy: GatewayAsyncService, diff --git a/framework/snippets/src/interactor/interactor_tx/interactor_run_trait.rs b/framework/snippets/src/interactor/interactor_tx/interactor_run_trait.rs index 3e1286305b..03bee0efa0 100644 --- a/framework/snippets/src/interactor/interactor_tx/interactor_run_trait.rs +++ b/framework/snippets/src/interactor/interactor_tx/interactor_run_trait.rs @@ -15,6 +15,7 @@ where interpreter_context: InterpreterContext::new().with_dir(self.current_dir.clone()), tx_id: None, tx_hash: None, + relayer: None, } } } diff --git a/framework/snippets/src/interactor/tx_output_file.rs b/framework/snippets/src/interactor/tx_output_file.rs new file mode 100644 index 0000000000..fc964d4172 --- /dev/null +++ b/framework/snippets/src/interactor/tx_output_file.rs @@ -0,0 +1,134 @@ +use anyhow::{Context, Result}; +use multiversx_sdk::{ + chain_core::{ + std::{base64_decode, new_address::compute_new_address_bech32}, + types::Address, + }, + data::transaction::{ApiTransactionResult, Transaction}, +}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +/// mxpy-compatible output format for a signed transaction. +/// +/// When saved to a file this can be re-loaded by `sc-meta tx send --infile ` +/// or by `mxpy tx send --infile `. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TxOutputFile { + #[serde(rename = "emittedTransaction")] + pub emitted_transaction: Transaction, + + /// Decoded (plain-text / UTF-8) representation of the transaction data field. + #[serde(rename = "emittedTransactionData")] + pub emitted_transaction_data: String, + + /// Populated after a successful broadcast; empty string when just serializing. + #[serde(rename = "emittedTransactionHash")] + pub emitted_transaction_hash: String, + + /// Populated for deploy transactions: the deterministically-computed contract address. + #[serde(rename = "contractAddress", skip_serializing_if = "Option::is_none")] + pub contract_address: Option, + + /// Populated after waiting for the transaction result on-network. + #[serde( + rename = "transactionOnNetwork", + skip_serializing_if = "Option::is_none" + )] + pub transaction_on_network: Option, +} + +impl TxOutputFile { + /// Creates an mxpy-compatible output value from a transaction. + pub fn from_transaction(emitted_transaction: Transaction) -> Result { + let emitted_transaction_data = match &emitted_transaction.data { + None => String::new(), + Some(data) => String::from_utf8_lossy(&base64_decode(data)?).into_owned(), + }; + let contract_address = tx_opt_deploy_new_address(&emitted_transaction); + + Ok(Self { + emitted_transaction, + emitted_transaction_data, + emitted_transaction_hash: String::new(), + contract_address, + transaction_on_network: None, + }) + } + + /// Write this output to `outfile`, or print to stdout when no outfile is given. + pub fn save_output(&self, outfile: Option<&Path>) -> Result<()> { + let json = to_json_pretty(self)?; + if let Some(path) = outfile { + std::fs::write(path, &json) + .with_context(|| format!("failed to write to {}", path.display()))?; + println!("Transaction saved to {}", path.display()); + } else { + println!("{json}"); + } + Ok(()) + } +} + +/// Returns the new contract address for a deploy transaction, otherwise returns `None`. +fn tx_opt_deploy_new_address(tx: &Transaction) -> Option { + if tx.receiver.address == Address::zero() { + Some(compute_new_address_bech32(&tx.sender, tx.nonce).to_bech32_string()) + } else { + None + } +} + +fn to_json_pretty(value: &T) -> Result { + let mut buf = Vec::new(); + let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut serializer = serde_json::Serializer::with_formatter(&mut buf, formatter); + value + .serialize(&mut serializer) + .context("failed to serialize transaction")?; + String::from_utf8(buf).context("non-UTF8 in serialized JSON") +} + +#[cfg(test)] +mod tests { + use super::*; + use multiversx_sdk::{chain_core::std::Bech32Address, data::transaction::TransactionVersion}; + + fn transaction(receiver: Bech32Address) -> Transaction { + Transaction { + nonce: 42, + value: "0".to_owned(), + receiver, + sender: Address::from([7; 32]).to_bech32_default(), + gas_price: 1_000_000_000, + gas_limit: 100_000, + data: None, + signature: None, + chain_id: "D".to_owned(), + version: TransactionVersion::V2, + options: None, + relayer: None, + relayer_signature: None, + } + } + + #[test] + fn derives_contract_address_for_zero_recipient() { + let transaction = transaction(Bech32Address::zero_default_hrp()); + let expected = + compute_new_address_bech32(&transaction.sender, transaction.nonce).to_bech32_string(); + + let output = TxOutputFile::from_transaction(transaction).unwrap(); + + assert_eq!(output.contract_address, Some(expected)); + } + + #[test] + fn does_not_set_contract_address_for_nonzero_recipient() { + let output = + TxOutputFile::from_transaction(transaction(Address::from([8; 32]).to_bech32_default())) + .unwrap(); + + assert_eq!(output.contract_address, None); + } +} diff --git a/framework/snippets/src/lib.rs b/framework/snippets/src/lib.rs index 91e4f6e64c..cd0a343cdd 100644 --- a/framework/snippets/src/lib.rs +++ b/framework/snippets/src/lib.rs @@ -4,10 +4,13 @@ #![allow(clippy::manual_is_multiple_of)] pub mod account_tool; +pub mod config; mod interactor; mod multi; pub mod network_response; +pub use config::*; + pub use env_logger; pub use hex; pub use interactor::*; diff --git a/framework/snippets/src/multi/interactor_multi_sc_process.rs b/framework/snippets/src/multi/interactor_multi_sc_process.rs index 2cc8acf811..a0bddac7f2 100644 --- a/framework/snippets/src/multi/interactor_multi_sc_process.rs +++ b/framework/snippets/src/multi/interactor_multi_sc_process.rs @@ -15,14 +15,13 @@ where { pub(crate) async fn recall_senders_nonce(&mut self, senders: HashSet
) { for sender_address in &senders { - let nonce = self.recall_nonce(sender_address).await; + let nonce = self.resolve_tx_nonce(sender_address).await; let sender = self .sender_map .get_mut(sender_address) .expect("sender not registered"); sender.current_nonce = Some(nonce); - println!("sender's recalled nonce: {nonce}"); } } @@ -34,13 +33,13 @@ where for tx in &txs { let tx_hash = self - .proxy + .proxy() .request(SendTxRequest(tx)) .await .expect("failed to send transaction"); println!("process tx hash: {tx_hash} with nonce: {}", tx.nonce); - futures.push(retrieve_tx_on_network(&self.proxy, tx_hash.clone())); + futures.push(retrieve_tx_on_network(self.proxy(), tx_hash.clone())); } self.generate_blocks(4).await.unwrap(); @@ -56,6 +55,9 @@ pub(crate) fn update_nonces_and_sign_tx(transaction: &mut Transaction, sender: & transaction.nonce = sender.current_nonce.unwrap(); sender.current_nonce = Some(sender.current_nonce.unwrap() + 1); - let signature = sender.wallet.sign_tx(&*transaction); - transaction.signature = Some(hex::encode(signature)); + let signature = sender + .wallet + .sign_tx(&*transaction) + .expect("failed to sign transaction"); + transaction.signature = Some(signature); } diff --git a/framework/snippets/src/network_response.rs b/framework/snippets/src/network_response.rs index 773caa5ca8..739a1d58f1 100644 --- a/framework/snippets/src/network_response.rs +++ b/framework/snippets/src/network_response.rs @@ -1,6 +1,6 @@ use crate::sdk::{ + chain_core::std::base64_decode, data::transaction::{ApiLogs, ApiSmartContractResult, ApiTransactionResult, Events}, - utils::base64_decode, }; use multiversx_sc_scenario::{ imports::{Address, ESDTSystemSCAddress, ReturnCode}, @@ -29,9 +29,11 @@ pub fn parse_tx_response(tx: ApiTransactionResult, return_code: ReturnCode) -> T fn process_signal_error(tx: &ApiTransactionResult, return_code: ReturnCode) -> TxResponseStatus { if let Some(event) = find_log(tx, LOG_IDENTIFIER_SIGNAL_ERROR) { if event.topics.len() >= 2 { - let error_message = String::from_utf8(base64_decode(&event.topics[1])).expect( - "Failed to decode base64-encoded error message from transaction event topic", - ); + let error_message = String::from_utf8( + base64_decode(&event.topics[1]) + .expect("Failed to base64-decode error message topic"), + ) + .expect("Failed to decode base64-encoded error message from transaction event topic"); return TxResponseStatus::new(return_code, &error_message); } } @@ -140,7 +142,9 @@ fn process_new_issued_token_identifier(tx: &ApiTransactionResult) -> Option) -> Option Vec> { let mut out = Vec::new(); event.data.for_each(|data_member| { - let decoded_data = String::from_utf8(base64_decode(data_member)).unwrap(); + let decoded_data = String::from_utf8(base64_decode(data_member).unwrap()).unwrap(); if decoded_data.starts_with('@') { let out_content = decode_scr_data_or_panic(decoded_data.as_str()); diff --git a/contracts/examples/adder/interactor/tests/basic_interactor_cs_test.rs b/framework/snippets/tests/set_state_cs_test.rs similarity index 54% rename from contracts/examples/adder/interactor/tests/basic_interactor_cs_test.rs rename to framework/snippets/tests/set_state_cs_test.rs index f7b7053107..19777227ef 100644 --- a/contracts/examples/adder/interactor/tests/basic_interactor_cs_test.rs +++ b/framework/snippets/tests/set_state_cs_test.rs @@ -1,51 +1,22 @@ -use basic_interactor::{BasicInteractor, Config}; -use multiversx_sc_snippets::{imports::Bech32Address, sdk::gateway::SetStateAccount, test_wallets}; +// use adder_interactor::{BasicInteractor, Config, GeneralConfig}; +use multiversx_sc_snippets::imports::*; use serial_test::serial; -#[tokio::test] -#[serial] -#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn simulator_upgrade_test() { - let mut basic_interact = BasicInteractor::new(Config::chain_simulator_config()).await; - - basic_interact.generate_blocks(2).await; - - basic_interact.deploy().await; - - basic_interact.generate_blocks(2).await; - - basic_interact.add(1u32).await; - - basic_interact.generate_blocks(2).await; - - // Sum will be 1 - let sum = basic_interact.get_sum().await; - assert_eq!(sum, 1u32.into()); - - basic_interact - .upgrade(7u32, &basic_interact.adder_owner_address.clone(), None) - .await; - - basic_interact.generate_blocks(2).await; - - // Sum will be the updated value of 7 - let sum = basic_interact.get_sum().await; - assert_eq!(sum, 7u32.into()); - - // Upgrade fails - basic_interact - .upgrade( - 10u32, - &basic_interact.wallet_address.clone(), - Some("upgrade is allowed only for owner"), - ) - .await; - - basic_interact.generate_blocks(2).await; +async fn cs_interactor() -> HttpInteractor { + HttpInteractor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&ConnectionConfig::chain_simulator()) + .await +} - // Sum will remain 7 - let sum = basic_interact.get_sum().await; - assert_eq!(sum, 7u32.into()); +async fn real_chain_reader_interactor() -> HttpInteractor { + HttpInteractor::empty() + .with_current_dir(env!("CARGO_MANIFEST_DIR")) + .with_config(&ConnectionConfig { + gateway_uri: "https://devnet-gateway.multiversx.com".to_owned(), + chain_type: ChainType::Real, + }) + .await } #[tokio::test] @@ -54,33 +25,26 @@ async fn simulator_upgrade_test() { async fn set_state_cs_test() { let account_address = test_wallets::mike(); - let real_chain_interact = BasicInteractor::new(Config::load_config()).await; - let simulator_interact = BasicInteractor::new(Config::chain_simulator_config()).await; + let real_chain_interact = real_chain_reader_interactor().await; + let simulator_interact = cs_interactor().await; let account = real_chain_interact - .interactor .get_account(&account_address.to_address()) .await; let pairs = real_chain_interact - .interactor .get_account_storage(&account_address.to_address()) .await; let set_state_account = SetStateAccount::from(account).with_storage(pairs); let vec_state = vec![set_state_account]; - let set_state_response = simulator_interact.interactor.set_state(vec_state).await; + let set_state_response = simulator_interact.set_state(vec_state).await; - simulator_interact - .interactor - .generate_blocks(2u64) - .await - .unwrap(); + simulator_interact.generate_blocks(2u64).await.unwrap(); assert!(set_state_response.is_ok()); let storage = simulator_interact - .interactor .get_account_storage(&account_address.to_address()) .await; @@ -96,35 +60,25 @@ async fn set_state_from_file_cs_test() { let account_address = test_wallets::mike(); let account_address_2 = test_wallets::ivan(); - let mut real_chain_interact = BasicInteractor::new(Config::load_config()).await; - let simulator_interact = BasicInteractor::new(Config::chain_simulator_config()).await; + let mut real_chain_interact = real_chain_reader_interactor().await; + let simulator_interact = cs_interactor().await; // now we should have current mike account in the set state file real_chain_interact - .interactor .retrieve_account(&Bech32Address::from(&account_address.to_address())) .await; real_chain_interact - .interactor .retrieve_account(&Bech32Address::from(&account_address_2.to_address())) .await; - let set_state_response = simulator_interact - .interactor - .set_state_for_saved_accounts() - .await; + let set_state_response = simulator_interact.set_state_for_saved_accounts().await; - simulator_interact - .interactor - .generate_blocks(2u64) - .await - .unwrap(); + simulator_interact.generate_blocks(2u64).await.unwrap(); assert!(set_state_response.is_ok()); let storage = simulator_interact - .interactor .get_account_storage(&account_address.to_address()) .await; @@ -140,35 +94,25 @@ async fn set_state_overwrite_cs_test() { let account_address = test_wallets::mike(); let account_address_2 = test_wallets::ivan(); - let mut real_chain_interact = BasicInteractor::new(Config::load_config()).await; - let simulator_interact = BasicInteractor::new(Config::chain_simulator_config()).await; + let mut real_chain_interact = real_chain_reader_interactor().await; + let simulator_interact = cs_interactor().await; // now we should have current mike and ivan accounts in the set state file real_chain_interact - .interactor .retrieve_account(&Bech32Address::from(&account_address.to_address())) .await; real_chain_interact - .interactor .retrieve_account(&Bech32Address::from(&account_address_2.to_address())) .await; - let set_state_response = simulator_interact - .interactor - .set_state_for_saved_accounts() - .await; + let set_state_response = simulator_interact.set_state_for_saved_accounts().await; - simulator_interact - .interactor - .generate_blocks(2u64) - .await - .unwrap(); + simulator_interact.generate_blocks(2u64).await.unwrap(); assert!(set_state_response.is_ok()); let storage = simulator_interact - .interactor .get_account_storage(&account_address.to_address()) .await; @@ -187,20 +131,14 @@ async fn set_state_overwrite_cs_test() { let overwrite_vec = vec![account_1, account_2]; simulator_interact - .interactor .set_state_overwrite(overwrite_vec) .await .unwrap(); - simulator_interact - .interactor - .generate_blocks(2u64) - .await - .unwrap(); + simulator_interact.generate_blocks(2u64).await.unwrap(); // verify keys let storage_1 = simulator_interact - .interactor .get_account_storage(&account_address.to_address()) .await; @@ -209,7 +147,6 @@ async fn set_state_overwrite_cs_test() { println!("mike's storage keys in chain simulator {:#?}", storage_1); let storage_2 = simulator_interact - .interactor .get_account_storage(&account_address.to_address()) .await; diff --git a/sdk/core/Cargo.toml b/sdk/core/Cargo.toml index dcfee6523c..60ecac4cbc 100644 --- a/sdk/core/Cargo.toml +++ b/sdk/core/Cargo.toml @@ -16,7 +16,7 @@ categories = ["cryptography::cryptocurrencies", "api-bindings"] keywords = ["multiversx", "blockchain", "sdk", "api"] [features] -wallet-full = ["rand", "uuid"] +ledger = ["dep:ledger-transport-hid", "dep:ledger-apdu"] [dependencies] serde = { version = "1.0", features = ["derive"] } @@ -28,20 +28,17 @@ sha2 = "0.11" sha3 = "0.12" hmac = "0.13" hex = "0.4" -base64 = "0.22" pbkdf2 = { version = "0.13", default-features = false } zeroize = "1.4" -itertools = "0.14" +itertools = "0.15" pem = "3.0" log = "0.4" scrypt = { version = "0.12", default-features = false } aes = "0.9" ctr = "0.10" -rpassword = "~7.3" # rpassword >=7.4 requires Rust 1.88 (let_chains), pin to 7.3.x for Rust 1.85 compat - -# Optional dependencies, for wallet-full -rand = { version = "0.10", optional = true, default-features = false } -uuid = { version = "1.10", features = ["v4"], optional = true } +bitflags = "2.13" +ledger-transport-hid = { version = "0.10", optional = true } +ledger-apdu = { version = "0.10", optional = true } [dependencies.multiversx-chain-core] version = "=0.23.1" diff --git a/sdk/core/src/crypto.rs b/sdk/core/src/crypto.rs deleted file mode 100644 index 9a2e0e5d63..0000000000 --- a/sdk/core/src/crypto.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod edwards25519; -pub mod private_key; -pub mod public_key; diff --git a/sdk/core/src/crypto/edwards25519.rs b/sdk/core/src/crypto/edwards25519.rs deleted file mode 100644 index 7aa881b235..0000000000 --- a/sdk/core/src/crypto/edwards25519.rs +++ /dev/null @@ -1,848 +0,0 @@ -#![allow(clippy::identity_op)] - -pub mod completed_group_element; -pub mod constant; -pub mod extended_group_element; -pub mod field_element; -pub mod pre_computed_group_element; -pub mod projective_group_element; - -fn load3(input: Vec) -> i64 { - let mut r = input[0] as i64; - r |= (input[1] as i64) << 8; - r |= (input[2] as i64) << 16; - r -} - -fn load4(input: Vec) -> i64 { - let mut r = input[0] as i64; - r |= (input[1] as i64) << 8; - r |= (input[2] as i64) << 16; - r |= (input[3] as i64) << 24; - r -} - -// The scalars are GF(2^252 + 27742317777372353535851937790883648493). - -// Input: -// a[0]+256*a[1]+...+256^31*a[31] = a -// b[0]+256*b[1]+...+256^31*b[31] = b -// c[0]+256*c[1]+...+256^31*c[31] = c -// -// Output: -// s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l -// where l = 2^252 + 27742317777372353535851937790883648493. -pub fn sc_mul_add(a: [u8; 32], b: [u8; 32], c: [u8; 32]) -> [u8; 32] { - let a0 = 2097151 & load3(a[..].to_vec()); - let a1 = 2097151 & (load4(a[2..].to_vec()) >> 5); - let a2 = 2097151 & (load3(a[5..].to_vec()) >> 2); - let a3 = 2097151 & (load4(a[7..].to_vec()) >> 7); - let a4 = 2097151 & (load4(a[10..].to_vec()) >> 4); - let a5 = 2097151 & (load3(a[13..].to_vec()) >> 1); - let a6 = 2097151 & (load4(a[15..].to_vec()) >> 6); - let a7 = 2097151 & (load3(a[18..].to_vec()) >> 3); - let a8 = 2097151 & load3(a[21..].to_vec()); - let a9 = 2097151 & (load4(a[23..].to_vec()) >> 5); - let a10 = 2097151 & (load3(a[26..].to_vec()) >> 2); - let a11 = load4(a[28..].to_vec()) >> 7; - let b0 = 2097151 & load3(b[..].to_vec()); - let b1 = 2097151 & (load4(b[2..].to_vec()) >> 5); - let b2 = 2097151 & (load3(b[5..].to_vec()) >> 2); - let b3 = 2097151 & (load4(b[7..].to_vec()) >> 7); - let b4 = 2097151 & (load4(b[10..].to_vec()) >> 4); - let b5 = 2097151 & (load3(b[13..].to_vec()) >> 1); - let b6 = 2097151 & (load4(b[15..].to_vec()) >> 6); - let b7 = 2097151 & (load3(b[18..].to_vec()) >> 3); - let b8 = 2097151 & load3(b[21..].to_vec()); - let b9 = 2097151 & (load4(b[23..].to_vec()) >> 5); - let b10 = 2097151 & (load3(b[26..].to_vec()) >> 2); - let b11 = load4(b[28..].to_vec()) >> 7; - let c0 = 2097151 & load3(c[..].to_vec()); - let c1 = 2097151 & (load4(c[2..].to_vec()) >> 5); - let c2 = 2097151 & (load3(c[5..].to_vec()) >> 2); - let c3 = 2097151 & (load4(c[7..].to_vec()) >> 7); - let c4 = 2097151 & (load4(c[10..].to_vec()) >> 4); - let c5 = 2097151 & (load3(c[13..].to_vec()) >> 1); - let c6 = 2097151 & (load4(c[15..].to_vec()) >> 6); - let c7 = 2097151 & (load3(c[18..].to_vec()) >> 3); - let c8 = 2097151 & load3(c[21..].to_vec()); - let c9 = 2097151 & (load4(c[23..].to_vec()) >> 5); - let c10 = 2097151 & (load3(c[26..].to_vec()) >> 2); - let c11 = load4(c[28..].to_vec()) >> 7; - let mut carry = [0i64; 23]; - - let mut s0 = c0 + a0 * b0; - let mut s1 = c1 + a0 * b1 + a1 * b0; - let mut s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; - let mut s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; - let mut s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; - let mut s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; - let mut s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; - let mut s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; - let mut s8 = c8 - + a0 * b8 - + a1 * b7 - + a2 * b6 - + a3 * b5 - + a4 * b4 - + a5 * b3 - + a6 * b2 - + a7 * b1 - + a8 * b0; - let mut s9 = c9 - + a0 * b9 - + a1 * b8 - + a2 * b7 - + a3 * b6 - + a4 * b5 - + a5 * b4 - + a6 * b3 - + a7 * b2 - + a8 * b1 - + a9 * b0; - let mut s10 = c10 - + a0 * b10 - + a1 * b9 - + a2 * b8 - + a3 * b7 - + a4 * b6 - + a5 * b5 - + a6 * b4 - + a7 * b3 - + a8 * b2 - + a9 * b1 - + a10 * b0; - let mut s11 = c11 - + a0 * b11 - + a1 * b10 - + a2 * b9 - + a3 * b8 - + a4 * b7 - + a5 * b6 - + a6 * b5 - + a7 * b4 - + a8 * b3 - + a9 * b2 - + a10 * b1 - + a11 * b0; - let mut s12 = a1 * b11 - + a2 * b10 - + a3 * b9 - + a4 * b8 - + a5 * b7 - + a6 * b6 - + a7 * b5 - + a8 * b4 - + a9 * b3 - + a10 * b2 - + a11 * b1; - let mut s13 = a2 * b11 - + a3 * b10 - + a4 * b9 - + a5 * b8 - + a6 * b7 - + a7 * b6 - + a8 * b5 - + a9 * b4 - + a10 * b3 - + a11 * b2; - let mut s14 = - a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; - let mut s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; - let mut s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; - let mut s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; - let mut s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; - let mut s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; - let mut s20 = a9 * b11 + a10 * b10 + a11 * b9; - let mut s21 = a10 * b11 + a11 * b10; - let mut s22 = a11 * b11; - let mut s23 = 0i64; - - carry[0] = (s0 + (1 << 20)) >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[2] = (s2 + (1 << 20)) >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[4] = (s4 + (1 << 20)) >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[6] = (s6 + (1 << 20)) >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[8] = (s8 + (1 << 20)) >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[10] = (s10 + (1 << 20)) >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - carry[12] = (s12 + (1 << 20)) >> 21; - s13 += carry[12]; - s12 -= carry[12] << 21; - carry[14] = (s14 + (1 << 20)) >> 21; - s15 += carry[14]; - s14 -= carry[14] << 21; - carry[16] = (s16 + (1 << 20)) >> 21; - s17 += carry[16]; - s16 -= carry[16] << 21; - carry[18] = (s18 + (1 << 20)) >> 21; - s19 += carry[18]; - s18 -= carry[18] << 21; - carry[20] = (s20 + (1 << 20)) >> 21; - s21 += carry[20]; - s20 -= carry[20] << 21; - carry[22] = (s22 + (1 << 20)) >> 21; - s23 += carry[22]; - s22 -= carry[22] << 21; - - carry[1] = (s1 + (1 << 20)) >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[3] = (s3 + (1 << 20)) >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[5] = (s5 + (1 << 20)) >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[7] = (s7 + (1 << 20)) >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[9] = (s9 + (1 << 20)) >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[11] = (s11 + (1 << 20)) >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - carry[13] = (s13 + (1 << 20)) >> 21; - s14 += carry[13]; - s13 -= carry[13] << 21; - carry[15] = (s15 + (1 << 20)) >> 21; - s16 += carry[15]; - s15 -= carry[15] << 21; - carry[17] = (s17 + (1 << 20)) >> 21; - s18 += carry[17]; - s17 -= carry[17] << 21; - carry[19] = (s19 + (1 << 20)) >> 21; - s20 += carry[19]; - s19 -= carry[19] << 21; - carry[21] = (s21 + (1 << 20)) >> 21; - s22 += carry[21]; - s21 -= carry[21] << 21; - - s11 += s23 * 666643; - s12 += s23 * 470296; - s13 += s23 * 654183; - s14 -= s23 * 997805; - s15 += s23 * 136657; - s16 -= s23 * 683901; - // s23 = 0; - - s10 += s22 * 666643; - s11 += s22 * 470296; - s12 += s22 * 654183; - s13 -= s22 * 997805; - s14 += s22 * 136657; - s15 -= s22 * 683901; - // s22 = 0; - - s9 += s21 * 666643; - s10 += s21 * 470296; - s11 += s21 * 654183; - s12 -= s21 * 997805; - s13 += s21 * 136657; - s14 -= s21 * 683901; - // s21 = 0; - - s8 += s20 * 666643; - s9 += s20 * 470296; - s10 += s20 * 654183; - s11 -= s20 * 997805; - s12 += s20 * 136657; - s13 -= s20 * 683901; - // s20 = 0; - - s7 += s19 * 666643; - s8 += s19 * 470296; - s9 += s19 * 654183; - s10 -= s19 * 997805; - s11 += s19 * 136657; - s12 -= s19 * 683901; - // s19 = 0; - - s6 += s18 * 666643; - s7 += s18 * 470296; - s8 += s18 * 654183; - s9 -= s18 * 997805; - s10 += s18 * 136657; - s11 -= s18 * 683901; - // s18 = 0; - - carry[6] = (s6 + (1 << 20)) >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[8] = (s8 + (1 << 20)) >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[10] = (s10 + (1 << 20)) >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - carry[12] = (s12 + (1 << 20)) >> 21; - s13 += carry[12]; - s12 -= carry[12] << 21; - carry[14] = (s14 + (1 << 20)) >> 21; - s15 += carry[14]; - s14 -= carry[14] << 21; - carry[16] = (s16 + (1 << 20)) >> 21; - s17 += carry[16]; - s16 -= carry[16] << 21; - - carry[7] = (s7 + (1 << 20)) >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[9] = (s9 + (1 << 20)) >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[11] = (s11 + (1 << 20)) >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - carry[13] = (s13 + (1 << 20)) >> 21; - s14 += carry[13]; - s13 -= carry[13] << 21; - carry[15] = (s15 + (1 << 20)) >> 21; - s16 += carry[15]; - s15 -= carry[15] << 21; - - s5 += s17 * 666643; - s6 += s17 * 470296; - s7 += s17 * 654183; - s8 -= s17 * 997805; - s9 += s17 * 136657; - s10 -= s17 * 683901; - // s17 = 0; - - s4 += s16 * 666643; - s5 += s16 * 470296; - s6 += s16 * 654183; - s7 -= s16 * 997805; - s8 += s16 * 136657; - s9 -= s16 * 683901; - // s16 = 0; - - s3 += s15 * 666643; - s4 += s15 * 470296; - s5 += s15 * 654183; - s6 -= s15 * 997805; - s7 += s15 * 136657; - s8 -= s15 * 683901; - // s15 = 0; - - s2 += s14 * 666643; - s3 += s14 * 470296; - s4 += s14 * 654183; - s5 -= s14 * 997805; - s6 += s14 * 136657; - s7 -= s14 * 683901; - // s14 = 0; - - s1 += s13 * 666643; - s2 += s13 * 470296; - s3 += s13 * 654183; - s4 -= s13 * 997805; - s5 += s13 * 136657; - s6 -= s13 * 683901; - // s13 = 0; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - s12 = 0; - - carry[0] = (s0 + (1 << 20)) >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[2] = (s2 + (1 << 20)) >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[4] = (s4 + (1 << 20)) >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[6] = (s6 + (1 << 20)) >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[8] = (s8 + (1 << 20)) >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[10] = (s10 + (1 << 20)) >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - - carry[1] = (s1 + (1 << 20)) >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[3] = (s3 + (1 << 20)) >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[5] = (s5 + (1 << 20)) >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[7] = (s7 + (1 << 20)) >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[9] = (s9 + (1 << 20)) >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[11] = (s11 + (1 << 20)) >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - s12 = 0; - - carry[0] = s0 >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[1] = s1 >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[2] = s2 >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[3] = s3 >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[4] = s4 >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[5] = s5 >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[6] = s6 >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[7] = s7 >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[8] = s8 >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[9] = s9 >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[10] = s10 >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - carry[11] = s11 >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - // s12 = 0; - - carry[0] = s0 >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[1] = s1 >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[2] = s2 >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[3] = s3 >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[4] = s4 >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[5] = s5 >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[6] = s6 >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[7] = s7 >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[8] = s8 >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[9] = s9 >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[10] = s10 >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - - [ - (s0 >> 0) as u8, - (s0 >> 8) as u8, - ((s0 >> 16) | (s1 << 5)) as u8, - (s1 >> 3) as u8, - (s1 >> 11) as u8, - ((s1 >> 19) | (s2 << 2)) as u8, - (s2 >> 6) as u8, - ((s2 >> 14) | (s3 << 7)) as u8, - (s3 >> 1) as u8, - (s3 >> 9) as u8, - ((s3 >> 17) | (s4 << 4)) as u8, - (s4 >> 4) as u8, - (s4 >> 12) as u8, - ((s4 >> 20) | (s5 << 1)) as u8, - (s5 >> 7) as u8, - ((s5 >> 15) | (s6 << 6)) as u8, - (s6 >> 2) as u8, - (s6 >> 10) as u8, - ((s6 >> 18) | (s7 << 3)) as u8, - (s7 >> 5) as u8, - (s7 >> 13) as u8, - (s8 >> 0) as u8, - (s8 >> 8) as u8, - ((s8 >> 16) | (s9 << 5)) as u8, - (s9 >> 3) as u8, - (s9 >> 11) as u8, - ((s9 >> 19) | (s10 << 2)) as u8, - (s10 >> 6) as u8, - ((s10 >> 14) | (s11 << 7)) as u8, - (s11 >> 1) as u8, - (s11 >> 9) as u8, - (s11 >> 17) as u8, - ] -} - -// Input: -// s[0]+256*s[1]+...+256^63*s[63] = s -// -// Output: -// s[0]+256*s[1]+...+256^31*s[31] = s mod l -// where l = 2^252 + 27742317777372353535851937790883648493. -pub fn sc_reduce(s: [u8; 64]) -> [u8; 32] { - let mut s0 = 2097151 & load3(s[..].to_vec()); - let mut s1 = 2097151 & (load4(s[2..].to_vec()) >> 5); - let mut s2 = 2097151 & (load3(s[5..].to_vec()) >> 2); - let mut s3 = 2097151 & (load4(s[7..].to_vec()) >> 7); - let mut s4 = 2097151 & (load4(s[10..].to_vec()) >> 4); - let mut s5 = 2097151 & (load3(s[13..].to_vec()) >> 1); - let mut s6 = 2097151 & (load4(s[15..].to_vec()) >> 6); - let mut s7 = 2097151 & (load3(s[18..].to_vec()) >> 3); - let mut s8 = 2097151 & load3(s[21..].to_vec()); - let mut s9 = 2097151 & (load4(s[23..].to_vec()) >> 5); - let mut s10 = 2097151 & (load3(s[26..].to_vec()) >> 2); - let mut s11 = 2097151 & (load4(s[28..].to_vec()) >> 7); - let mut s12 = 2097151 & (load4(s[31..].to_vec()) >> 4); - let mut s13 = 2097151 & (load3(s[34..].to_vec()) >> 1); - let mut s14 = 2097151 & (load4(s[36..].to_vec()) >> 6); - let mut s15 = 2097151 & (load3(s[39..].to_vec()) >> 3); - let mut s16 = 2097151 & load3(s[42..].to_vec()); - let mut s17 = 2097151 & (load4(s[44..].to_vec()) >> 5); - let s18 = 2097151 & (load3(s[47..].to_vec()) >> 2); - let s19 = 2097151 & (load4(s[49..].to_vec()) >> 7); - let s20 = 2097151 & (load4(s[52..].to_vec()) >> 4); - let s21 = 2097151 & (load3(s[55..].to_vec()) >> 1); - let s22 = 2097151 & (load4(s[57..].to_vec()) >> 6); - let s23 = load4(s[60..].to_vec()) >> 3; - - s11 += s23 * 666643; - s12 += s23 * 470296; - s13 += s23 * 654183; - s14 -= s23 * 997805; - s15 += s23 * 136657; - s16 -= s23 * 683901; - // s23 = 0; - - s10 += s22 * 666643; - s11 += s22 * 470296; - s12 += s22 * 654183; - s13 -= s22 * 997805; - s14 += s22 * 136657; - s15 -= s22 * 683901; - // s22 = 0; - - s9 += s21 * 666643; - s10 += s21 * 470296; - s11 += s21 * 654183; - s12 -= s21 * 997805; - s13 += s21 * 136657; - s14 -= s21 * 683901; - // s21 = 0; - - s8 += s20 * 666643; - s9 += s20 * 470296; - s10 += s20 * 654183; - s11 -= s20 * 997805; - s12 += s20 * 136657; - s13 -= s20 * 683901; - // s20 = 0; - - s7 += s19 * 666643; - s8 += s19 * 470296; - s9 += s19 * 654183; - s10 -= s19 * 997805; - s11 += s19 * 136657; - s12 -= s19 * 683901; - // s19 = 0; - - s6 += s18 * 666643; - s7 += s18 * 470296; - s8 += s18 * 654183; - s9 -= s18 * 997805; - s10 += s18 * 136657; - s11 -= s18 * 683901; - // s18 = 0; - - let mut carry = [0i64; 17]; - - carry[6] = (s6 + (1 << 20)) >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[8] = (s8 + (1 << 20)) >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[10] = (s10 + (1 << 20)) >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - carry[12] = (s12 + (1 << 20)) >> 21; - s13 += carry[12]; - s12 -= carry[12] << 21; - carry[14] = (s14 + (1 << 20)) >> 21; - s15 += carry[14]; - s14 -= carry[14] << 21; - carry[16] = (s16 + (1 << 20)) >> 21; - s17 += carry[16]; - s16 -= carry[16] << 21; - - carry[7] = (s7 + (1 << 20)) >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[9] = (s9 + (1 << 20)) >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[11] = (s11 + (1 << 20)) >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - carry[13] = (s13 + (1 << 20)) >> 21; - s14 += carry[13]; - s13 -= carry[13] << 21; - carry[15] = (s15 + (1 << 20)) >> 21; - s16 += carry[15]; - s15 -= carry[15] << 21; - - s5 += s17 * 666643; - s6 += s17 * 470296; - s7 += s17 * 654183; - s8 -= s17 * 997805; - s9 += s17 * 136657; - s10 -= s17 * 683901; - // s17 = 0; - - s4 += s16 * 666643; - s5 += s16 * 470296; - s6 += s16 * 654183; - s7 -= s16 * 997805; - s8 += s16 * 136657; - s9 -= s16 * 683901; - // s16 = 0; - - s3 += s15 * 666643; - s4 += s15 * 470296; - s5 += s15 * 654183; - s6 -= s15 * 997805; - s7 += s15 * 136657; - s8 -= s15 * 683901; - // s15 = 0; - - s2 += s14 * 666643; - s3 += s14 * 470296; - s4 += s14 * 654183; - s5 -= s14 * 997805; - s6 += s14 * 136657; - s7 -= s14 * 683901; - // s14 = 0; - - s1 += s13 * 666643; - s2 += s13 * 470296; - s3 += s13 * 654183; - s4 -= s13 * 997805; - s5 += s13 * 136657; - s6 -= s13 * 683901; - // s13 = 0; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - s12 = 0; - - carry[0] = (s0 + (1 << 20)) >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[2] = (s2 + (1 << 20)) >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[4] = (s4 + (1 << 20)) >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[6] = (s6 + (1 << 20)) >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[8] = (s8 + (1 << 20)) >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[10] = (s10 + (1 << 20)) >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - - carry[1] = (s1 + (1 << 20)) >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[3] = (s3 + (1 << 20)) >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[5] = (s5 + (1 << 20)) >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[7] = (s7 + (1 << 20)) >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[9] = (s9 + (1 << 20)) >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[11] = (s11 + (1 << 20)) >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - s12 = 0; - - carry[0] = s0 >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[1] = s1 >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[2] = s2 >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[3] = s3 >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[4] = s4 >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[5] = s5 >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[6] = s6 >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[7] = s7 >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[8] = s8 >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[9] = s9 >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[10] = s10 >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - carry[11] = s11 >> 21; - s12 += carry[11]; - s11 -= carry[11] << 21; - - s0 += s12 * 666643; - s1 += s12 * 470296; - s2 += s12 * 654183; - s3 -= s12 * 997805; - s4 += s12 * 136657; - s5 -= s12 * 683901; - // s12 = 0; - - carry[0] = s0 >> 21; - s1 += carry[0]; - s0 -= carry[0] << 21; - carry[1] = s1 >> 21; - s2 += carry[1]; - s1 -= carry[1] << 21; - carry[2] = s2 >> 21; - s3 += carry[2]; - s2 -= carry[2] << 21; - carry[3] = s3 >> 21; - s4 += carry[3]; - s3 -= carry[3] << 21; - carry[4] = s4 >> 21; - s5 += carry[4]; - s4 -= carry[4] << 21; - carry[5] = s5 >> 21; - s6 += carry[5]; - s5 -= carry[5] << 21; - carry[6] = s6 >> 21; - s7 += carry[6]; - s6 -= carry[6] << 21; - carry[7] = s7 >> 21; - s8 += carry[7]; - s7 -= carry[7] << 21; - carry[8] = s8 >> 21; - s9 += carry[8]; - s8 -= carry[8] << 21; - carry[9] = s9 >> 21; - s10 += carry[9]; - s9 -= carry[9] << 21; - carry[10] = s10 >> 21; - s11 += carry[10]; - s10 -= carry[10] << 21; - - [ - (s0 >> 0) as u8, - (s0 >> 8) as u8, - ((s0 >> 16) | (s1 << 5)) as u8, - (s1 >> 3) as u8, - (s1 >> 11) as u8, - ((s1 >> 19) | (s2 << 2)) as u8, - (s2 >> 6) as u8, - ((s2 >> 14) | (s3 << 7)) as u8, - (s3 >> 1) as u8, - (s3 >> 9) as u8, - ((s3 >> 17) | (s4 << 4)) as u8, - (s4 >> 4) as u8, - (s4 >> 12) as u8, - ((s4 >> 20) | (s5 << 1)) as u8, - (s5 >> 7) as u8, - ((s5 >> 15) | (s6 << 6)) as u8, - (s6 >> 2) as u8, - (s6 >> 10) as u8, - ((s6 >> 18) | (s7 << 3)) as u8, - (s7 >> 5) as u8, - (s7 >> 13) as u8, - (s8 >> 0) as u8, - (s8 >> 8) as u8, - ((s8 >> 16) | (s9 << 5)) as u8, - (s9 >> 3) as u8, - (s9 >> 11) as u8, - ((s9 >> 19) | (s10 << 2)) as u8, - (s10 >> 6) as u8, - ((s10 >> 14) | (s11 << 7)) as u8, - (s11 >> 1) as u8, - (s11 >> 9) as u8, - (s11 >> 17) as u8, - ] -} diff --git a/sdk/core/src/crypto/edwards25519/completed_group_element.rs b/sdk/core/src/crypto/edwards25519/completed_group_element.rs deleted file mode 100644 index 4341850b98..0000000000 --- a/sdk/core/src/crypto/edwards25519/completed_group_element.rs +++ /dev/null @@ -1,42 +0,0 @@ -use super::{ - extended_group_element::ExtendedGroupElement, field_element::FieldElement, - pre_computed_group_element::PreComputedGroupElement, - projective_group_element::ProjectiveGroupElement, -}; - -#[derive(Default)] -pub struct CompletedGroupElement { - pub x: FieldElement, - pub y: FieldElement, - pub z: FieldElement, - pub t: FieldElement, -} - -impl CompletedGroupElement { - pub fn ge_mixed_add(&mut self, p: &ExtendedGroupElement, q: &PreComputedGroupElement) { - let mut t0 = FieldElement::default(); - self.x.fe_add(&p.y, &p.x); - self.y.fe_sub(&p.y, &p.x); - self.z.fe_mul(&self.x, &q.y_plus_x); - self.y.fe_mul(&self.y.clone(), &q.y_minus_x); - self.t.fe_mul(&q.xy2d, &p.t); - t0.fe_add(&p.z, &p.z); - self.x.fe_sub(&self.z, &self.y); - self.y.fe_add(&self.z, &self.y.clone()); - self.z.fe_add(&t0, &self.t); - self.t.fe_sub(&t0, &self.t.clone()); - } - - pub fn to_extended(&self, r: &mut ExtendedGroupElement) { - r.x.fe_mul(&self.x, &self.t); - r.y.fe_mul(&self.y, &self.z); - r.z.fe_mul(&self.z, &self.t); - r.t.fe_mul(&self.x, &self.y); - } - - pub fn to_projective(&self, r: &mut ProjectiveGroupElement) { - r.x.fe_mul(&self.x, &self.t); - r.y.fe_mul(&self.y, &self.z); - r.z.fe_mul(&self.z, &self.t); - } -} diff --git a/sdk/core/src/crypto/edwards25519/constant.rs b/sdk/core/src/crypto/edwards25519/constant.rs deleted file mode 100644 index cb8d9e36b7..0000000000 --- a/sdk/core/src/crypto/edwards25519/constant.rs +++ /dev/null @@ -1,3654 +0,0 @@ -use super::{field_element::FieldElement, pre_computed_group_element::PreComputedGroupElement}; - -// base contains precomputed multiples of the base-point. See the Ed25519 paper -// for a discussion about how these values are used. -pub static BASE: [[PreComputedGroupElement; 8]; 32] = [ - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, - -6079156, 2047605, - ]), - y_minus_x: FieldElement([ - -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, - 19500929, -15469378, - ]), - xy2d: FieldElement([ - -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, - -24514362, -4438546, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, - -3814571, -358445, -10211303, - ]), - y_minus_x: FieldElement([ - -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, - -26829678, -5319081, - ]), - xy2d: FieldElement([ - 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, - -15472047, -4166697, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, - 28944400, -1550024, - ]), - y_minus_x: FieldElement([ - 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, - 7689662, 11199574, - ]), - xy2d: FieldElement([ - 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, - -17749093, -9920357, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, - 3284568, -6276540, - ]), - y_minus_x: FieldElement([ - 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, - 13059162, 10374397, - ]), - xy2d: FieldElement([ - 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, - -641708, -101325, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, - -15438304, 10819380, - ]), - y_minus_x: FieldElement([ - 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, - -12668491, 5581306, - ]), - xy2d: FieldElement([ - 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, - -23678021, -15815942, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, - -9818940, -12085777, - ]), - y_minus_x: FieldElement([ - -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, - 1762328, 14866737, - ]), - xy2d: FieldElement([ - -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, - 27914454, 4383652, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, - -23952439, -15175766, - ]), - y_minus_x: FieldElement([ - -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, - 30598449, 7715701, - ]), - xy2d: FieldElement([ - 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, - 29794553, -1409300, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, - -33154098, 2381726, - ]), - y_minus_x: FieldElement([ - -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, - -32300832, 15351955, - ]), - xy2d: FieldElement([ - 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, - 29551813, 10109425, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, - 10092783, -4764171, - ]), - y_minus_x: FieldElement([ - 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, - -17810127, 12803510, - ]), - xy2d: FieldElement([ - 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, - 18016357, 4397660, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, - -17477504, 982639, - ]), - y_minus_x: FieldElement([ - 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, - -21047696, 9934963, - ]), - xy2d: FieldElement([ - 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, - -30123922, -10897950, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, - -13338309, 5500568, - ]), - y_minus_x: FieldElement([ - 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, - 10874051, 13524335, - ]), - xy2d: FieldElement([ - 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, - -22528059, 5376628, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, - 4535768, 1569007, - ]), - y_minus_x: FieldElement([ - -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, - 31848280, 12543772, - ]), - xy2d: FieldElement([ - -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, - 7718482, 14474653, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, - 24316168, -5253567, - ]), - y_minus_x: FieldElement([ - 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, - -20729456, 8321686, - ]), - xy2d: FieldElement([ - 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, - -9984458, 608372, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, - 27229399, 23887, - ]), - y_minus_x: FieldElement([ - -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, - -1633405, 16678954, - ]), - xy2d: FieldElement([ - -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, - -17169265, 4904953, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, - 2128236, -4326833, - ]), - y_minus_x: FieldElement([ - -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, - -6882542, -2986532, - ]), - xy2d: FieldElement([ - -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, - 15736322, 4143876, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, - 23527084, -16458268, - ]), - y_minus_x: FieldElement([ - 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, - -15188911, -14416214, - ]), - xy2d: FieldElement([ - 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, - -4774191, -16323038, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, - 7390890, 2759800, - ]), - y_minus_x: FieldElement([ - 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, - 1244380, -12919645, - ]), - xy2d: FieldElement([ - -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, - 15886429, 16489664, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, - -30606455, -712933, - ]), - y_minus_x: FieldElement([ - -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, - -30836462, 5113182, - ]), - xy2d: FieldElement([ - -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, - 13847711, 5387222, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, - -19600662, 10370991, - ]), - y_minus_x: FieldElement([ - 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, - 32232924, 16763880, - ]), - xy2d: FieldElement([ - 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, - 3140038, -16510092, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, - -28774454, 366295, - ]), - y_minus_x: FieldElement([ - 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, - 719605, 11671788, - ]), - xy2d: FieldElement([ - 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, - 27000813, -10195553, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, - 6750977, -14521026, - ]), - y_minus_x: FieldElement([ - 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, - 8169720, 16220347, - ]), - xy2d: FieldElement([ - -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, - -5990708, -14166033, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, - 2655861, 1738395, - ]), - y_minus_x: FieldElement([ - -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, - -19087449, -11005278, - ]), - xy2d: FieldElement([ - 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, - 10478196, 8544890, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, - 17789017, -3395995, - ]), - y_minus_x: FieldElement([ - -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, - 30460520, 1052596, - ]), - xy2d: FieldElement([ - -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, - 3179268, -9478891, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, - -16411740, 19072640, -9511060, - ]), - y_minus_x: FieldElement([ - 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, - 473099, 5040608, - ]), - xy2d: FieldElement([ - -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, - -19558642, -3131606, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, - -27868496, 11538389, - ]), - y_minus_x: FieldElement([ - -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, - -5676054, 5797016, - ]), - xy2d: FieldElement([ - -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, - -2369172, -5877341, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, - 15123619, 10811505, - ]), - y_minus_x: FieldElement([ - 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, - -28886779, -11974553, - ]), - xy2d: FieldElement([ - -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, - 4714547, -9600655, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, - -11745876, 12340220, - ]), - y_minus_x: FieldElement([ - 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, - 15370987, 9608631, - ]), - xy2d: FieldElement([ - -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, - -28210621, -8814099, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, - 20571223, 8420556, - ]), - y_minus_x: FieldElement([ - 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, - -21574435, -12476749, - ]), - xy2d: FieldElement([ - 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, - 15069930, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, - -30663140, -2331391, - ]), - y_minus_x: FieldElement([ - 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, - -33069337, 9234253, - ]), - xy2d: FieldElement([ - 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, - 18979186, 13396066, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, - 33514650, 7021958, - ]), - y_minus_x: FieldElement([ - -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, - -21480480, 12868082, - ]), - xy2d: FieldElement([ - -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, - -12455797, -8089383, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, - -15233648, 5540521, - ]), - y_minus_x: FieldElement([ - -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, - -16128528, -14962807, - ]), - xy2d: FieldElement([ - 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, - -11513277, -15205948, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, - -19179614, 5867134, - ]), - y_minus_x: FieldElement([ - -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, - -29749703, -16108455, - ]), - xy2d: FieldElement([ - 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, - -19526700, 7734629, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, - 18549497, 15302069, - ]), - y_minus_x: FieldElement([ - -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, - -8872242, 8424746, - ]), - xy2d: FieldElement([ - 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, - -11284482, -828919, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, - -5066034, 16498837, - ]), - y_minus_x: FieldElement([ - 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, - 6536641, 10543906, - ]), - xy2d: FieldElement([ - -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, - -27235709, 8876771, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, - -604426, -9039817, - ]), - y_minus_x: FieldElement([ - 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, - 3084098, - ]), - xy2d: FieldElement([ - 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, - -30233575, 15272409, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, - -25014757, 1950504, - ]), - y_minus_x: FieldElement([ - -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, - 15341279, 8373727, - ]), - xy2d: FieldElement([ - 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, - -2878207, 15190420, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, - -26033313, -6875003, - ]), - y_minus_x: FieldElement([ - -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, - -16247779, -12154742, 6048605, - ]), - xy2d: FieldElement([ - -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, - 11213262, 9168384, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, - 20470157, -16398701, - ]), - y_minus_x: FieldElement([ - -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, - 14042978, 5230683, - ]), - xy2d: FieldElement([ - 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, - -5933891, -12449708, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, - -28539189, 15326563, - ]), - y_minus_x: FieldElement([ - -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, - -20514118, 9168260, - ]), - xy2d: FieldElement([ - -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, - 33087103, -9011387, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, - -15000531, -5996870, 15664672, - ]), - y_minus_x: FieldElement([ - 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, - 18151676, 13417686, - ]), - xy2d: FieldElement([ - -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, - 15271676, -15452665, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, - -6921301, -13440182, - ]), - y_minus_x: FieldElement([ - -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, - 12215110, 12028277, - ]), - xy2d: FieldElement([ - 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, - -16900089, -655628, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, - 23973261, -12684474, - ]), - y_minus_x: FieldElement([ - -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, - 32013174, -10103539, - ]), - xy2d: FieldElement([ - -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, - 4425632, -837822, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, - 4725005, 14044970, - ]), - y_minus_x: FieldElement([ - 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, - -19522291, -16109756, - ]), - xy2d: FieldElement([ - -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, - 9282262, 10282508, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, - 5542595, -10702683, - ]), - y_minus_x: FieldElement([ - -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, - -18093885, 10114655, - ]), - xy2d: FieldElement([ - -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, - 25953725, -106158, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, - -3315279, 12831125, - ]), - y_minus_x: FieldElement([ - -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, - 7381791, -2421839, - ]), - xy2d: FieldElement([ - -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, - -15362489, 12339664, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, - 25091167, 14856294, - ]), - y_minus_x: FieldElement([ - -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, - -22182473, -9012899, - ]), - xy2d: FieldElement([ - -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, - 30146206, 9142070, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, - -8237858, 4171294, - ]), - y_minus_x: FieldElement([ - -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, - 345228, -5462949, - ]), - xy2d: FieldElement([ - -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, - -16253183, -13582083, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, - 28012650, -16405420, - ]), - y_minus_x: FieldElement([ - -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, - -8867157, 3507940, - ]), - xy2d: FieldElement([ - 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, - -9164929, 6580396, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, - -2095802, 9304567, - ]), - y_minus_x: FieldElement([ - 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, - 5289421, -477127, - ]), - xy2d: FieldElement([ - -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, - 26939669, -3752294, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, - -3698806, 117887, - ]), - y_minus_x: FieldElement([ - 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, - -23319780, 541964, - ]), - xy2d: FieldElement([ - 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, - -13337066, -13552195, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, - 690426, 14876244, - ]), - y_minus_x: FieldElement([ - 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, - -22380984, 6618999, - ]), - xy2d: FieldElement([ - -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, - 32239829, -5652762, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, - 32779359, 5095274, - ]), - y_minus_x: FieldElement([ - -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, - 21639561, -2630236, - ]), - xy2d: FieldElement([ - -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, - 17874574, 558605, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, - 2085892, 5119761, - ]), - y_minus_x: FieldElement([ - -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, - -31999993, -5759884, - ]), - xy2d: FieldElement([ - -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, - -22351378, 12961482, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, - -30194652, -5159638, - ]), - y_minus_x: FieldElement([ - -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, - 7589640, 8945490, - ]), - xy2d: FieldElement([ - -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, - 24099109, -14456170, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, - -6231896, -10280736, - ]), - y_minus_x: FieldElement([ - 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, - -3363451, -6441124, - ]), - xy2d: FieldElement([ - -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, - -13866408, 7421392, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, - 28826358, -4123029, - ]), - y_minus_x: FieldElement([ - 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, - 13746021, -1742048, - ]), - xy2d: FieldElement([ - 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, - 4771362, -8419958, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, - 3569627, 11342593, - ]), - y_minus_x: FieldElement([ - 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, - 7325975, -14801071, - ]), - xy2d: FieldElement([ - -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, - -17646694, -8186692, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, - 9280358, -3973687, - ]), - y_minus_x: FieldElement([ - -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, - -11181622, -15545091, 4387441, - ]), - xy2d: FieldElement([ - -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, - 20617071, -7482001, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, - 24345683, 10325460, - ]), - y_minus_x: FieldElement([ - -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, - 4766743, 3552007, - ]), - xy2d: FieldElement([ - -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, - 10988822, -3994762, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, - -8295852, 13296005, - ]), - y_minus_x: FieldElement([ - -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, - -30000824, 12074674, - ]), - xy2d: FieldElement([ - 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, - 29832613, -16391035, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, - -27122660, 13101590, - ]), - y_minus_x: FieldElement([ - -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, - -30356070, -4190957, - ]), - xy2d: FieldElement([ - -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, - 19568978, 9628812, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, - -6291786, 3437740, - ]), - y_minus_x: FieldElement([ - -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, - 7463304, 4176122, - ]), - xy2d: FieldElement([ - -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, - 24216882, 5944158, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, - 11632993, 5847885, - ]), - y_minus_x: FieldElement([ - 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, - -9727230, 4782140, - ]), - xy2d: FieldElement([ - 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, - 6358847, -1873857, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, - -13538503, 1387155, - ]), - y_minus_x: FieldElement([ - 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, - -21866831, 11835260, - ]), - xy2d: FieldElement([ - 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, - -26560550, 5052483, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, - 12228557, -7003677, - ]), - y_minus_x: FieldElement([ - 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, - -6114803, 8653815, - ]), - xy2d: FieldElement([ - 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, - 28483275, 2841751, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, - 19932058, -12739203, - ]), - y_minus_x: FieldElement([ - -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, - -4777469, -13910208, - ]), - xy2d: FieldElement([ - 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, - -14013818, 3093230, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, - -24049421, -6691850, - ]), - y_minus_x: FieldElement([ - -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, - -21652117, -16739389, - ]), - xy2d: FieldElement([ - -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, - 17361620, 11864968, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, - -25578460, -16240689, - ]), - y_minus_x: FieldElement([ - 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, - 9145645, -6443880, - ]), - xy2d: FieldElement([ - 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, - -5652273, -3050304, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, - -27999779, 1803632, - ]), - y_minus_x: FieldElement([ - -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, - -21405489, 7047412, - ]), - xy2d: FieldElement([ - 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, - -32343828, -10257566, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, - -21679658, 2288038, - ]), - y_minus_x: FieldElement([ - -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, - -24819617, 12570232, - ]), - xy2d: FieldElement([ - -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, - -11974704, 4724943, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, - -29576300, 1903856, - ]), - y_minus_x: FieldElement([ - 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, - 26955097, 14109738, - ]), - xy2d: FieldElement([ - 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, - 31960942, 11934971, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, - 10491392, 1379718, - ]), - y_minus_x: FieldElement([ - -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, - 21432314, 12180697, - ]), - xy2d: FieldElement([ - -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, - -10301319, -13872883, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, - 26128231, 6032912, - ]), - y_minus_x: FieldElement([ - -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, - -25875034, -10437358, - ]), - xy2d: FieldElement([ - 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, - 12376617, 3188849, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, - -16109234, -9852307, - ]), - y_minus_x: FieldElement([ - -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, - 18640741, -960977, - ]), - xy2d: FieldElement([ - -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, - 10530747, 1053335, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, - -2948985, 24018831, 15026644, - ]), - y_minus_x: FieldElement([ - -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, - -2314791, -15145616, - ]), - xy2d: FieldElement([ - -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, - -23166419, -2531735, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, - -5070217, 726099, - ]), - y_minus_x: FieldElement([ - 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, - 30007388, -15823341, - ]), - xy2d: FieldElement([ - -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, - 4555336, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, - -11985298, 12422646, - ]), - y_minus_x: FieldElement([ - 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, - 29120153, 13924425, - ]), - xy2d: FieldElement([ - -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, - 7240931, -237388, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, - 3293637, -15551743, - ]), - y_minus_x: FieldElement([ - -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, - 12796905, -6335822, - ]), - xy2d: FieldElement([ - -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, - 3222231, -11160462, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, - -7550776, 2484985, - ]), - y_minus_x: FieldElement([ - 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, - -19802075, -3034702, - ]), - xy2d: FieldElement([ - -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, - -30097688, -10618797, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, - -7360503, -4109293, - ]), - y_minus_x: FieldElement([ - 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, - -23659143, -8132100, - ]), - xy2d: FieldElement([ - 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, - 13243957, 8709688, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, - 5150968, 7274186, - ]), - y_minus_x: FieldElement([ - 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, - 31021603, -9793610, - ]), - xy2d: FieldElement([ - -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, - 15067285, -14147707, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, - -19379462, -12403220, - ]), - y_minus_x: FieldElement([ - 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, - -7220461, 16527025, - ]), - xy2d: FieldElement([ - 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, - 23834301, 6588044, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, - -20314580, -1305992, - ]), - y_minus_x: FieldElement([ - -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, - 1976123, -7249027, - ]), - xy2d: FieldElement([ - 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, - 12331345, -8237197, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, - 10008136, -4667901, - ]), - y_minus_x: FieldElement([ - 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, - 8684155, -10532952, - ]), - xy2d: FieldElement([ - 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, - 31316348, 14219878, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, - 29126555, 9207390, - ]), - y_minus_x: FieldElement([ - 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, - -7957600, -14435730, - ]), - xy2d: FieldElement([ - 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, - 28346258, 1994730, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, - 22628102, 8115180, - ]), - y_minus_x: FieldElement([ - -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, - -9943017, 930272, - ]), - xy2d: FieldElement([ - -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, - -22765376, -10650715, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, - -16554220, -1867018, 8398970, - ]), - y_minus_x: FieldElement([ - -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, - 18009408, -15772772, - ]), - xy2d: FieldElement([ - -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, - 19288549, 1325865, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, - 33166107, -8042750, - ]), - y_minus_x: FieldElement([ - 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, - 2213263, -13878373, - ]), - xy2d: FieldElement([ - 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, - -5766928, 8371348, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, - -26442943, 10486144, - ]), - y_minus_x: FieldElement([ - -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, - -23921530, -11455195, - ]), - xy2d: FieldElement([ - 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, - 19985175, -3436086, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, - 65030, 8370684, - ]), - y_minus_x: FieldElement([ - -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, - 12743482, -9800518, - ]), - xy2d: FieldElement([ - -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, - 18800704, 255233, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, - 19258688, -14753793, - ]), - y_minus_x: FieldElement([ - -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, - -1524053, 2055794, - ]), - xy2d: FieldElement([ - 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, - -30021019, 7394435, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, - -4821741, 14799921, - ]), - y_minus_x: FieldElement([ - 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, - -9056018, 7402518, - ]), - xy2d: FieldElement([ - 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, - 8205061, 1607563, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, - 30019587, -9029278, - ]), - y_minus_x: FieldElement([ - -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, - 9944378, 8024, - ]), - xy2d: FieldElement([ - 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, - -8142388, 5640030, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, - 15219798, -14327783, - ]), - y_minus_x: FieldElement([ - 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, - -18342183, 9742717, - ]), - xy2d: FieldElement([ - 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, - 1994844, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, - 24319929, -6446333, - ]), - y_minus_x: FieldElement([ - 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, - -30102368, -4739048, - ]), - xy2d: FieldElement([ - 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, - 21880021, -12250760, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, - 8840445, 4957760, - ]), - y_minus_x: FieldElement([ - -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, - -14796503, 5005757, - ]), - xy2d: FieldElement([ - -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, - -16395286, -2176112, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, - 22208662, 2000468, - ]), - y_minus_x: FieldElement([ - 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, - -3691214, -7414184, - ]), - xy2d: FieldElement([ - 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, - -3132688, 16400289, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, - 26894937, 9132066, - ]), - y_minus_x: FieldElement([ - 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, - -6817084, -2692882, - ]), - xy2d: FieldElement([ - 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, - -4688006, 2364226, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, - 15445875, -7798101, - ]), - y_minus_x: FieldElement([ - 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, - -278050, -15759279, - ]), - xy2d: FieldElement([ - -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, - -29828287, -10815811, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, - 17293797, 960709, - ]), - y_minus_x: FieldElement([ - 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, - -19384511, 7639714, - ]), - xy2d: FieldElement([ - -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, - 29994677, -15808121, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, - -31184575, 709464, - ]), - y_minus_x: FieldElement([ - 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, - 16102007, 13205847, - ]), - xy2d: FieldElement([ - 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, - 10151379, 10394400, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, - -33302334, 8934414, - ]), - y_minus_x: FieldElement([ - -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, - -11536886, 11721158, - ]), - xy2d: FieldElement([ - 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, - -9205489, -1280045, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, - 6108462, -6183415, - ]), - y_minus_x: FieldElement([ - -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, - -26898490, -7867459, - ]), - xy2d: FieldElement([ - -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, - 7592688, -14992079, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, - -28256052, 4298412, - ]), - y_minus_x: FieldElement([ - -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, - 13717174, 10805743, - ]), - xy2d: FieldElement([ - -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, - -26938930, -5863836, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, - -4381056, 9882022, - ]), - y_minus_x: FieldElement([ - 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, - -23967430, -3299429, - ]), - xy2d: FieldElement([ - -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, - -2403099, 5276065, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, - 1449224, 13082861, - ]), - y_minus_x: FieldElement([ - 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, - -21016438, -8202000, - ]), - xy2d: FieldElement([ - -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, - -10300080, -11060101, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, - -22664288, 6904815, - ]), - y_minus_x: FieldElement([ - 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, - -29003639, -6657642, - ]), - xy2d: FieldElement([ - 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, - -25584551, 5181966, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, - -2322852, -12388574, - ]), - y_minus_x: FieldElement([ - -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, - 20603771, -6561742, - ]), - xy2d: FieldElement([ - -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, - 4662781, 7820689, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, - 16136752, 15264020, - ]), - y_minus_x: FieldElement([ - -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, - 19012087, 3772772, - ]), - xy2d: FieldElement([ - 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, - 20527771, 12988982, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, - 3299665, -12424953, - ]), - y_minus_x: FieldElement([ - -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, - -25583147, 8991218, - ]), - xy2d: FieldElement([ - -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, - -30242287, 1507265, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, - 14606362, -10647073, - ]), - y_minus_x: FieldElement([ - -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, - 4170404, -2085325, - ]), - xy2d: FieldElement([ - -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, - -14276837, -8400798, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, - 13240845, 10965870, - ]), - y_minus_x: FieldElement([ - -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, - 29514390, 4302863, - ]), - xy2d: FieldElement([ - -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, - 17846988, -13971927, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, - -5222701, 12650267, - ]), - y_minus_x: FieldElement([ - -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, - -19134326, 10958663, - ]), - xy2d: FieldElement([ - 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, - -25083830, 4271862, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, - -32526221, 8469673, - ]), - y_minus_x: FieldElement([ - 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, - 24154791, -9460943, - ]), - xy2d: FieldElement([ - 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, - 24913809, 9815020, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, - 18735128, 9466238, - ]), - y_minus_x: FieldElement([ - 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, - -22728655, 16199064, - ]), - xy2d: FieldElement([ - 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, - 30801119, 2164795, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, - -13492807, 1268052, - ]), - y_minus_x: FieldElement([ - -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, - -17055369, 3565904, - ]), - xy2d: FieldElement([ - 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, - -27162222, -14030531, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, - 2244111, -14001979, - ]), - y_minus_x: FieldElement([ - -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, - 13491506, 4641841, - ]), - xy2d: FieldElement([ - 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, - 14476989, -12767431, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, - -27912810, 12651324, - ]), - y_minus_x: FieldElement([ - -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, - 21721536, -3148940, - ]), - xy2d: FieldElement([ - 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, - 13216206, 14842320, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, - -27434397, 9900184, - ]), - y_minus_x: FieldElement([ - 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, - -7051866, -8040114, - ]), - xy2d: FieldElement([ - 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, - 9524356, -7018878, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, - 15663516, 4035784, - ]), - y_minus_x: FieldElement([ - -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, - -22258478, 4659091, - ]), - xy2d: FieldElement([ - -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, - -2178256, -13455585, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, - 23440562, -290208, - ]), - y_minus_x: FieldElement([ - 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, - -16571960, -7442864, - ]), - xy2d: FieldElement([ - 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, - -3646624, 3898661, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, - 21831162, 1245233, - ]), - y_minus_x: FieldElement([ - 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, - -30402091, -16716212, - ]), - xy2d: FieldElement([ - -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, - -22338025, 13987525, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, - -7469781, -2858068, - ]), - y_minus_x: FieldElement([ - 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, - -14581012, 4091397, - ]), - xy2d: FieldElement([ - -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, - 29077877, -14741988, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, - -20017847, 2357889, - ]), - y_minus_x: FieldElement([ - 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, - 5727338, 189038, - ]), - xy2d: FieldElement([ - 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, - -25888931, -14884697, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, - 13913677, -5137875, - ]), - y_minus_x: FieldElement([ - -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, - 12878652, 8511905, - ]), - xy2d: FieldElement([ - -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, - 5568676, -3127656, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, - -14395196, 8070818, - ]), - y_minus_x: FieldElement([ - 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, - -11552749, -1028714, - ]), - xy2d: FieldElement([ - 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, - -27493162, 15431203, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, - -15056790, -7935931, - ]), - y_minus_x: FieldElement([ - -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, - -3356550, 9594024, - ]), - xy2d: FieldElement([ - -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, - 13352335, -10977084, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, - -13215537, -319204, - ]), - y_minus_x: FieldElement([ - 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, - -22750759, 14523817, - ]), - xy2d: FieldElement([ - 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, - -30172742, -4805667, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, - -27727044, 11358504, - ]), - y_minus_x: FieldElement([ - -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, - -26123651, 4985768, - ]), - xy2d: FieldElement([ - -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, - -19414307, -15621255, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, - -1691065, -9004790, - ]), - y_minus_x: FieldElement([ - 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, - -16796596, -5031438, - ]), - xy2d: FieldElement([ - -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, - -99853, -10237333, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, - -29260363, -5511971, - ]), - y_minus_x: FieldElement([ - 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, - 4882242, -10626905, - ]), - xy2d: FieldElement([ - 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, - -5190932, -4162409, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, - 330070, -3659409, - ]), - y_minus_x: FieldElement([ - 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, - -271295, 12071499, - ]), - xy2d: FieldElement([ - -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, - -5159697, 3829363, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, - 26333140, 14267664, - ]), - y_minus_x: FieldElement([ - -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, - -21761224, 8930324, - ]), - xy2d: FieldElement([ - -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, - -26491501, -16408940, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, - -15943699, 13814990, - ]), - y_minus_x: FieldElement([ - -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, - -1956526, -1776914, - ]), - xy2d: FieldElement([ - -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, - -18685978, 4578290, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, - 21964432, 8235257, - ]), - y_minus_x: FieldElement([ - -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, - 13006806, 2355433, - ]), - xy2d: FieldElement([ - -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, - 1141648, -12796236, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, - 32674895, 13715045, - ]), - y_minus_x: FieldElement([ - 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, - -28740881, -15642093, - ]), - xy2d: FieldElement([ - -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, - -27245943, 4383347, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, - 27193557, 6245191, - ]), - y_minus_x: FieldElement([ - -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, - 22510453, 8577507, - ]), - xy2d: FieldElement([ - -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, - -29571492, -3635906, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, - 5611860, 8164018, - ]), - y_minus_x: FieldElement([ - -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, - 5773085, -8422109, - ]), - xy2d: FieldElement([ - -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, - 31632953, 190926, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, - -25341555, -3627528, - ]), - y_minus_x: FieldElement([ - 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, - -1672488, 827625, - ]), - xy2d: FieldElement([ - -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, - -24878478, 1541286, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, - 20456845, -1885033, - ]), - y_minus_x: FieldElement([ - 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, - 29439641, 15138866, - ]), - xy2d: FieldElement([ - 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, - 7779328, 109896, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, - 23177719, -554075, - ]), - y_minus_x: FieldElement([ - 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, - 4439158, -13279347, - ]), - xy2d: FieldElement([ - -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, - -17717757, -5461437, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, - 8550082, -15114165, - ]), - y_minus_x: FieldElement([ - -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, - -23260460, -8428588, - ]), - xy2d: FieldElement([ - -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, - -21902570, 1494193, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, - 8742704, 12967017, - ]), - y_minus_x: FieldElement([ - -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, - -29903758, 15553883, - ]), - xy2d: FieldElement([ - 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, - 19375923, -12647961, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, - 2862653, 9455043, - ]), - y_minus_x: FieldElement([ - 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, - 15511449, 4789663, - ]), - xy2d: FieldElement([ - -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, - 23513200, 16652362, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, - -18319198, 10212860, - ]), - y_minus_x: FieldElement([ - 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, - -9988298, -12506466, - ]), - xy2d: FieldElement([ - -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, - -2499677, -16710063, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, - 2719757, 4940997, - ]), - y_minus_x: FieldElement([ - -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, - 26699843, 5276295, - ]), - xy2d: FieldElement([ - -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, - -15452774, 7159369, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, - 22477218, -8403385, - ]), - y_minus_x: FieldElement([ - 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, - -22548173, 9334109, - ]), - xy2d: FieldElement([ - 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, - -22587149, 536906, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, - 10838060, -15420424, - ]), - y_minus_x: FieldElement([ - -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, - 6378260, 699185, - ]), - xy2d: FieldElement([ - 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, - -5045064, -13373962, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, - -3918636, -9669325, - ]), - y_minus_x: FieldElement([ - 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, - -21786507, 5427593, - ]), - xy2d: FieldElement([ - 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, - 19236243, 12477404, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, - -23169516, 7733644, - ]), - y_minus_x: FieldElement([ - 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, - 10322027, 15313801, - ]), - xy2d: FieldElement([ - 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, - -24449242, 10890804, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, - 25180797, -5334884, - ]), - y_minus_x: FieldElement([ - -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, - 316878, 13820577, - ]), - xy2d: FieldElement([ - -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, - 30696930, -3712849, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, - -32349517, 7392473, - ]), - y_minus_x: FieldElement([ - -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, - 25112947, -2926644, - ]), - xy2d: FieldElement([ - -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, - 5537438, -13914319, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, - 31234590, 6090599, - ]), - y_minus_x: FieldElement([ - -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, - -6970405, -9034768, - ]), - xy2d: FieldElement([ - -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, - 20650474, 1804084, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, - 27842616, -202328, - ]), - y_minus_x: FieldElement([ - -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, - -11375082, 12714369, - ]), - xy2d: FieldElement([ - 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, - -19733229, 12796920, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, - -12899438, 3480665, -15182815, - ]), - y_minus_x: FieldElement([ - -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, - -33374054, 2771025, - ]), - xy2d: FieldElement([ - -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, - -24552282, 15673397, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, - 3727144, -12934448, - ]), - y_minus_x: FieldElement([ - 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, - 31123697, -10958981, - ]), - xy2d: FieldElement([ - 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, - -16432438, 9648165, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, - -26008332, -11377501, - ]), - y_minus_x: FieldElement([ - 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, - 2662509, -16297073, - ]), - xy2d: FieldElement([ - -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, - 32247248, -14389861, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, - -16027623, -13378845, - ]), - y_minus_x: FieldElement([ - -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, - 17597934, 2346211, - ]), - xy2d: FieldElement([ - 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, - 3059833, -11782870, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, - -33502212, 3592096, - ]), - y_minus_x: FieldElement([ - 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, - -982665, -6837803, - ]), - xy2d: FieldElement([ - -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, - 26353178, 693168, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, - 31375464, 14369965, - ]), - y_minus_x: FieldElement([ - -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, - 17901441, 16011505, - ]), - xy2d: FieldElement([ - 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, - -16046376, 8764035, 12309598, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, - 17544096, -10593782, - ]), - y_minus_x: FieldElement([ - 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, - -5565381, 12348900, - ]), - xy2d: FieldElement([ - -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, - -10632534, -585479, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, - 30944593, 1130208, - ]), - y_minus_x: FieldElement([ - 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, - 23550156, -271232, - ]), - xy2d: FieldElement([ - 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, - -408818, -137719, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, - 3260525, -7166271, - ]), - y_minus_x: FieldElement([ - -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, - -12748722, 2701326, - ]), - xy2d: FieldElement([ - -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, - 11329923, 1862132, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, - -9037963, -940300, - ]), - y_minus_x: FieldElement([ - 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, - -15438168, 11595570, - ]), - xy2d: FieldElement([ - 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, - -13489462, -4363670, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, - 977108, 699994, - ]), - y_minus_x: FieldElement([ - -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, - -30534533, -14337913, - ]), - xy2d: FieldElement([ - 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, - 12493932, -5409317, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, - 29375955, 6024730, - ]), - y_minus_x: FieldElement([ - 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, - -9940361, 2854096, - ]), - xy2d: FieldElement([ - -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, - 12121869, 16648078, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, - 20237806, 2838411, - ]), - y_minus_x: FieldElement([ - -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, - 1068881, -12499905, - ]), - xy2d: FieldElement([ - -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, - 12521378, 4845654, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, - -6482306, -12885870, - ]), - y_minus_x: FieldElement([ - -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, - -3113227, 13974498, - ]), - xy2d: FieldElement([ - 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, - 18895762, 12629579, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, - 32195181, 3895677, - ]), - y_minus_x: FieldElement([ - 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, - -8982069, 4429647, - ]), - xy2d: FieldElement([ - -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, - 18047436, -15281743, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, - -12850837, -17620701, -9408468, - ]), - y_minus_x: FieldElement([ - 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, - -29958985, 8773375, - ]), - xy2d: FieldElement([ - -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, - -7874389, -13920155, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, - -1721788, -2776725, - ]), - y_minus_x: FieldElement([ - -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, - 28408820, 6816612, - ]), - xy2d: FieldElement([ - -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, - -10339570, 5067943, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, - 5331210, -10105944, - ]), - y_minus_x: FieldElement([ - 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, - 27679908, -1648928, - ]), - xy2d: FieldElement([ - 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, - 22611444, -12715406, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, - 4854859, 6622139, - ]), - y_minus_x: FieldElement([ - -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, - 26404409, 13001963, - ]), - xy2d: FieldElement([ - -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, - -15405155, 11020693, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, - 28761762, 1406734, - ]), - y_minus_x: FieldElement([ - -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, - 25577411, -13378680, - ]), - xy2d: FieldElement([ - -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, - -9368926, 4745410, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, - 10931924, -11931931, - ]), - y_minus_x: FieldElement([ - -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, - -20470770, 13434654, - ]), - xy2d: FieldElement([ - 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, - 28445307, -5364710, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, - 9074234, 1167180, - ]), - y_minus_x: FieldElement([ - -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, - 3843903, 9367684, - ]), - xy2d: FieldElement([ - -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, - -139197, 4242895, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, - -27417366, 16625501, - ]), - y_minus_x: FieldElement([ - -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, - -27351616, 14247413, - ]), - xy2d: FieldElement([ - 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, - 27108877, -1180880, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, - -27148451, 981874, - ]), - y_minus_x: FieldElement([ - 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, - -10479804, -16197962, - ]), - xy2d: FieldElement([ - -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, - -15933690, 3797899, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, - 20491983, -8042152, - ]), - y_minus_x: FieldElement([ - 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, - 30997318, -6703063, - ]), - xy2d: FieldElement([ - 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, - 17649998, -250080, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, - -2233253, 7662146, - ]), - y_minus_x: FieldElement([ - -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, - -3174674, 3440183, - ]), - xy2d: FieldElement([ - -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, - 4862400, 1133, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, - -30594991, -7379421, - ]), - y_minus_x: FieldElement([ - -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, - 18278453, 15405622, - ]), - xy2d: FieldElement([ - -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, - -21875062, 13626197, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, - 13340839, 15928663, - ]), - y_minus_x: FieldElement([ - 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, - -23452306, 5964753, - ]), - xy2d: FieldElement([ - 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, - 2538215, -7570755, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, - -629256, -15958862, - ]), - y_minus_x: FieldElement([ - -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, - 11659922, -11115118, - ]), - xy2d: FieldElement([ - 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, - 33100372, -1306171, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, - -5931810, -14466380, - ]), - y_minus_x: FieldElement([ - 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, - -10735770, -10039824, - ]), - xy2d: FieldElement([ - 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, - 18036436, 5803270, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, - -6159431, -14117438, - ]), - y_minus_x: FieldElement([ - -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, - -20095739, 11763584, - ]), - xy2d: FieldElement([ - -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, - -19773211, -10713562, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, - -17020157, 992471, - ]), - y_minus_x: FieldElement([ - 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, - -11574335, -10601610, - ]), - xy2d: FieldElement([ - 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, - 26224780, 16452269, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, - -20533829, 3698650, - ]), - y_minus_x: FieldElement([ - 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, - 27394301, 12015369, - ]), - xy2d: FieldElement([ - 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, - -13259127, -3402461, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, - -8550524, -10393462, - ]), - y_minus_x: FieldElement([ - -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, - -4976164, 12651793, - ]), - xy2d: FieldElement([ - -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, - 9768698, -2533218, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, - 4940095, 10678226, - ]), - y_minus_x: FieldElement([ - 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, - -24372348, 16582019, - ]), - xy2d: FieldElement([ - -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, - -11003761, 7989037, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, - -19686113, -14800171, - ]), - y_minus_x: FieldElement([ - -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, - 28550068, -6857132, - ]), - xy2d: FieldElement([ - -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, - 1155602, 5988841, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, - 24618407, 8283181, - ]), - y_minus_x: FieldElement([ - -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, - 1466169, 10740210, - ]), - xy2d: FieldElement([ - -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, - -32164649, -15327040, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, - -3260321, -573935, - ]), - y_minus_x: FieldElement([ - 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, - -7380369, -6144105, - ]), - xy2d: FieldElement([ - -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, - -14453128, -1625486, -6494814, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, - -9906200, -621852, - ]), - y_minus_x: FieldElement([ - 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, - -6171428, -15186581, - ]), - xy2d: FieldElement([ - -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, - -1558923, -9863646, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, - -30581476, -15757844, - ]), - y_minus_x: FieldElement([ - 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, - 24003793, 14264025, - ]), - xy2d: FieldElement([ - 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, - 13958495, -5732453, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, - 7340521, -15410068, - ]), - y_minus_x: FieldElement([ - 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, - -15066784, -10375192, - ]), - xy2d: FieldElement([ - -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, - -8843694, 3849921, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, - 5002294, -15550259, - ]), - y_minus_x: FieldElement([ - -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, - -25845716, 12741426, - ]), - xy2d: FieldElement([ - -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, - 28834118, -7646072, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, - 20461858, 5491305, - ]), - y_minus_x: FieldElement([ - 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, - 11293807, -28588204, -9421832, - ]), - xy2d: FieldElement([ - 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, - 29867745, -8795943, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, - 22853429, -4012011, - ]), - y_minus_x: FieldElement([ - 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, - 12876623, -2112447, - ]), - xy2d: FieldElement([ - 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, - 16031844, 3723494, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, - 23896954, -4314245, - ]), - y_minus_x: FieldElement([ - -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, - -9919646, -8826859, - ]), - xy2d: FieldElement([ - 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, - -18051778, -2082915, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, - -19715240, 7847707, - ]), - y_minus_x: FieldElement([ - 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, - -10020567, 3852848, - ]), - xy2d: FieldElement([ - -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, - 29330899, -15076224, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, - -27548796, 12314391, - ]), - y_minus_x: FieldElement([ - 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, - 16103996, -3731215, - ]), - xy2d: FieldElement([ - -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, - -29815713, -9841101, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, - -25205859, 2739713, - ]), - y_minus_x: FieldElement([ - 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, - 4931255, 11987849, - ]), - xy2d: FieldElement([ - -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, - -30076310, 10117930, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, - 12890019, 15728940, - ]), - y_minus_x: FieldElement([ - -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, - 12885167, 8311031, - ]), - xy2d: FieldElement([ - -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, - 1888765, -5435404, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, - -12707869, -19464434, -3340243, - ]), - y_minus_x: FieldElement([ - -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, - 17151279, -9854116, - ]), - xy2d: FieldElement([ - -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, - -16866484, 16176525, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, - -28746253, -10197509, - ]), - y_minus_x: FieldElement([ - -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, - 32808310, 1099883, - ]), - xy2d: FieldElement([ - 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, - 2051441, -15225865, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, - -22686354, 16633660, - ]), - y_minus_x: FieldElement([ - 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, - 8402478, -9864273, - ]), - xy2d: FieldElement([ - -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, - 17983010, 9967138, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, - 17507396, 3651560, - ]), - y_minus_x: FieldElement([ - -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, - -18553322, -11357135, - ]), - xy2d: FieldElement([ - 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, - -5605463, -7621941, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, - 17258084, -7977739, - ]), - y_minus_x: FieldElement([ - 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, - 26908270, 12150756, - ]), - xy2d: FieldElement([ - -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, - -32302074, 16215819, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, - -7503072, -8675347, - ]), - y_minus_x: FieldElement([ - -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, - 21676107, -1943028, - ]), - xy2d: FieldElement([ - 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, - -3556191, -7913075, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, - -18907032, -9662799, - ]), - y_minus_x: FieldElement([ - -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, - 25459437, -4564609, - ]), - xy2d: FieldElement([ - -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, - -6432418, -1644817, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, - 6335692, 7249989, - ]), - y_minus_x: FieldElement([ - -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, - 25993170, -12478523, - ]), - xy2d: FieldElement([ - 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, - 31820368, 15075278, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, - -17970238, 12833045, - ]), - y_minus_x: FieldElement([ - 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, - -9413939, 14905377, - ]), - xy2d: FieldElement([ - 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, - 27628530, -7555480, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, - -14110875, 15297016, - ]), - y_minus_x: FieldElement([ - 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, - 8683221, 2921426, - ]), - xy2d: FieldElement([ - 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, - 4674690, 13890525, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, - 4389687, 288396, - ]), - y_minus_x: FieldElement([ - 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, - 23388070, 16052080, - ]), - xy2d: FieldElement([ - 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, - -14754271, -10812892, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, - -28822128, 929275, - ]), - y_minus_x: FieldElement([ - 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, - -9371869, -21393143, 2465074, - ]), - xy2d: FieldElement([ - 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, - 2463391, -4622140, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, - 4003896, 12673717, - ]), - y_minus_x: FieldElement([ - -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, - 7903886, 2348101, - ]), - xy2d: FieldElement([ - 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, - -28377538, 10048127, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, - -32297756, 15221632, - ]), - y_minus_x: FieldElement([ - -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, - 29769758, 6593415, - ]), - xy2d: FieldElement([ - -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, - 30958054, 8292160, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, - 26143136, -3148876, - ]), - y_minus_x: FieldElement([ - 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, - -3758243, -2304625, - ]), - xy2d: FieldElement([ - -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, - -16664475, 8194478, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, - 28572286, 3005164, - ]), - y_minus_x: FieldElement([ - 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, - -26094821, -13079595, - ]), - xy2d: FieldElement([ - -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, - 32208683, -1198248, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, - -27672585, -11539858, - ]), - y_minus_x: FieldElement([ - 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, - 15397331, -4130193, - ]), - xy2d: FieldElement([ - 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, - -27505566, 15087184, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, - 16788528, -5868942, - ]), - y_minus_x: FieldElement([ - -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, - 31322514, -11615635, - ]), - xy2d: FieldElement([ - 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, - 13040862, -12112948, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, - 1613711, 4896935, - ]), - y_minus_x: FieldElement([ - -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, - -23971776, -11267415, - ]), - xy2d: FieldElement([ - -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, - -23753030, -8436416, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, - 23097949, -566018, - ]), - y_minus_x: FieldElement([ - 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, - -18512313, 2424778, - ]), - xy2d: FieldElement([ - 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, - -3642553, 12412659, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, - -3141501, 11179385, - ]), - y_minus_x: FieldElement([ - 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, - -18684645, -11443503, - ]), - xy2d: FieldElement([ - 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, - 11052904, 5219329, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, - 9014762, -8579056, - ]), - y_minus_x: FieldElement([ - -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, - 8473550, -3256838, - ]), - xy2d: FieldElement([ - 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, - -24568481, 11788948, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, - -24568470, -1458691, - ]), - y_minus_x: FieldElement([ - -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, - -18513325, 8464118, - ]), - xy2d: FieldElement([ - -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, - 33313881, -8370517, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, - -8940735, -5818269, - ]), - y_minus_x: FieldElement([ - -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, - 28358192, -4253904, - ]), - xy2d: FieldElement([ - -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, - -5351664, 5596589, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, - 1768301, 15373193, - ]), - y_minus_x: FieldElement([ - -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, - -24660491, 3442910, - ]), - xy2d: FieldElement([ - -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, - -18585478, 13365930, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, - -9689599, -3031667, - ]), - y_minus_x: FieldElement([ - 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, - 15723479, -15163481, - ]), - xy2d: FieldElement([ - -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, - 519526, -1235876, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, - 16648397, 7605640, - ]), - y_minus_x: FieldElement([ - -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, - -9468848, 4763278, - ]), - xy2d: FieldElement([ - -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, - -11356529, 728112, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, - -29365436, -5498272, - ]), - y_minus_x: FieldElement([ - 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, - 10750447, 10014012, - ]), - xy2d: FieldElement([ - -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, - 3424691, 7540221, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, - -9244265, 15258046, - ]), - y_minus_x: FieldElement([ - 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, - 1062915, -5136345, - ]), - xy2d: FieldElement([ - -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, - 32960380, 1459310, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, - 18638003, -11174937, - ]), - y_minus_x: FieldElement([ - 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, - -6643087, -5442636, - ]), - xy2d: FieldElement([ - -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, - -32294889, -6456008, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, - -7852844, -8138429, - ]), - y_minus_x: FieldElement([ - -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, - -9431476, 5203576, - ]), - xy2d: FieldElement([ - 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, - 31809243, 7347066, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, - 15192876, -2087490, - ]), - y_minus_x: FieldElement([ - -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, - 33473243, -13382104, - ]), - xy2d: FieldElement([ - 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, - -6135662, 14480053, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, - 27595050, 8737275, - ]), - y_minus_x: FieldElement([ - -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, - 26498114, 66511, - ]), - xy2d: FieldElement([ - 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, - -13327787, -7515095, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, - -17127465, 15115439, - ]), - y_minus_x: FieldElement([ - 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, - 8236921, 16492939, - ]), - xy2d: FieldElement([ - -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, - 6708380, -6222424, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, - 29955601, -11678310, - ]), - y_minus_x: FieldElement([ - 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, - -9917575, 6216608, - ]), - xy2d: FieldElement([ - -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, - -26767480, 7525079, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, - -19386633, 11994101, - ]), - y_minus_x: FieldElement([ - 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, - -7477437, 13381418, - ]), - xy2d: FieldElement([ - 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, - 28535282, 15779576, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, - 9955285, -16303356, - ]), - y_minus_x: FieldElement([ - 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, - 20194861, 13380996, - ]), - xy2d: FieldElement([ - -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, - 26342023, 10146099, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, - 21612326, -545728, - ]), - y_minus_x: FieldElement([ - -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, - -20856566, 11649658, - ]), - xy2d: FieldElement([ - -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, - 33514190, 2333242, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, - 24350578, -13450001, - ]), - y_minus_x: FieldElement([ - -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, - 8317860, 12352766, - ]), - xy2d: FieldElement([ - 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, - 20712163, 6719373, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, - 26224235, -3297458, - ]), - y_minus_x: FieldElement([ - -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, - 18620611, -16428628, - ]), - xy2d: FieldElement([ - -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, - -30701573, -16479657, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, - 13735342, 1934062, - ]), - y_minus_x: FieldElement([ - 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, - -21277997, 5473616, - ]), - xy2d: FieldElement([ - 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, - 29111212, -5451014, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, - -10295587, 452383, - ]), - y_minus_x: FieldElement([ - -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, - 15766062, 8407814, - ]), - xy2d: FieldElement([ - -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, - -8067909, 2276718, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, - 22740376, -7303417, - ]), - y_minus_x: FieldElement([ - 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, - -15637599, 13295222, - ]), - xy2d: FieldElement([ - 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, - -4451357, -14669444, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, - 7770569, 9620597, - ]), - y_minus_x: FieldElement([ - 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, - -33502340, -14767970, - ]), - xy2d: FieldElement([ - 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, - 30494170, -11440799, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, - -1684339, -13333647, - ]), - y_minus_x: FieldElement([ - 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, - 3353509, 4033511, - ]), - xy2d: FieldElement([ - -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, - 27485041, 7356032, - ]), - }, - ], - [ - PreComputedGroupElement { - y_plus_x: FieldElement([ - 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, - 28431036, -16771834, - ]), - y_minus_x: FieldElement([ - -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, - 15970762, 4099461, - ]), - xy2d: FieldElement([ - 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, - 11465739, 8317062, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, - 20969334, -5157516, - ]), - y_minus_x: FieldElement([ - -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, - 11177095, 14989547, - ]), - xy2d: FieldElement([ - -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, - -28487508, 9930240, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, - -13403753, 16291481, -5314038, - ]), - y_minus_x: FieldElement([ - -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, - 4368891, 9788741, - ]), - xy2d: FieldElement([ - 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, - 16250552, -11111103, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, - -30190403, 4782747, - ]), - y_minus_x: FieldElement([ - -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, - 22546403, 437323, - ]), - xy2d: FieldElement([ - 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, - -30633590, -14097016, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, - -19284671, -6114373, - ]), - y_minus_x: FieldElement([ - 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, - -29380133, 11754228, - ]), - xy2d: FieldElement([ - -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, - 22761615, -10134141, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, - 32851222, 11717399, - ]), - y_minus_x: FieldElement([ - 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, - -1193175, -4030831, - ]), - xy2d: FieldElement([ - -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, - -19985279, -3948376, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, - -30772034, -15486313, - ]), - y_minus_x: FieldElement([ - -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, - 10928917, 3011958, - ]), - xy2d: FieldElement([ - -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, - 18008031, 10258577, - ]), - }, - PreComputedGroupElement { - y_plus_x: FieldElement([ - -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, - 25127874, 6671743, - ]), - y_minus_x: FieldElement([ - 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, - 25838796, 4642684, - ]), - xy2d: FieldElement([ - -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, - 18423289, 4177476, - ]), - }, - ], -]; diff --git a/sdk/core/src/crypto/edwards25519/extended_group_element.rs b/sdk/core/src/crypto/edwards25519/extended_group_element.rs deleted file mode 100644 index 6315fc5627..0000000000 --- a/sdk/core/src/crypto/edwards25519/extended_group_element.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::{ - completed_group_element::CompletedGroupElement, field_element::FieldElement, - pre_computed_group_element::PreComputedGroupElement, - projective_group_element::ProjectiveGroupElement, -}; - -#[derive(Default, Copy, Clone, Debug)] -pub struct ExtendedGroupElement { - pub x: FieldElement, - pub y: FieldElement, - pub z: FieldElement, - pub t: FieldElement, -} - -impl ExtendedGroupElement { - pub fn to_projective(self, r: &mut ProjectiveGroupElement) { - r.x.fe_copy(&self.x); - r.y.fe_copy(&self.y); - r.z.fe_copy(&self.z); - } - - pub fn double(&self, r: &mut CompletedGroupElement) { - let mut q = ProjectiveGroupElement::default(); - - self.to_projective(&mut q); - q.double(r); - } - - pub fn zero(&mut self) { - self.x.fe_zero(); - self.y.fe_one(); - self.z.fe_one(); - self.t.fe_zero(); - } - - // ge_scalar_mult_base computes h = a*B, where - // a = a[0]+256*a[1]+...+256^31 a[31] - // B is the Ed25519 base point (x,4/5) with x positive. - // - // Preconditions: - // a[31] <= 127 - #[allow(clippy::needless_range_loop)] - pub fn ge_scalar_mult_base(&mut self, a: [u8; 32]) { - let mut e = [0i8; 64]; - for (i, v) in a.iter().enumerate() { - e[2 * i] = (v & 15) as i8; - e[2 * i + 1] = ((v >> 4) & 15) as i8; - } - - // each e[i] is between 0 and 15 and e[63] is between 0 and 7. - - let mut carry: i8 = 0; - for i in 0..63 { - e[i] += carry; - carry = (e[i] + 8) >> 4; - e[i] -= carry << 4; - } - - e[63] += carry; - // each e[i] is between -8 and 8. - - self.zero(); - let mut t = PreComputedGroupElement::default(); - let mut r = CompletedGroupElement::default(); - for i in (1..64).step_by(2) { - t.select_point(i / 2, e[i as usize] as i32); - r.ge_mixed_add(self, &t); - r.to_extended(self); - } - - let mut s = ProjectiveGroupElement::default(); - - self.double(&mut r); - r.to_projective(&mut s); - s.double(&mut r); - r.to_projective(&mut s); - s.double(&mut r); - r.to_projective(&mut s); - s.double(&mut r); - r.to_extended(self); - - for i in (0..64).step_by(2) { - t.select_point(i / 2, e[i as usize] as i32); - r.ge_mixed_add(self, &t); - r.to_extended(self); - } - } - - pub fn to_bytes(self) -> [u8; 32] { - let mut recip = FieldElement::default(); - let mut x = FieldElement::default(); - let mut y = FieldElement::default(); - - recip.fe_invert(&self.z); - x.fe_mul(&self.x, &recip); - y.fe_mul(&self.y, &recip); - let mut s = y.to_bytes(); - - s[31] ^= x.fe_is_negative() << 7; - - s - } -} diff --git a/sdk/core/src/crypto/edwards25519/field_element.rs b/sdk/core/src/crypto/edwards25519/field_element.rs deleted file mode 100644 index afa56c4dda..0000000000 --- a/sdk/core/src/crypto/edwards25519/field_element.rs +++ /dev/null @@ -1,637 +0,0 @@ -use std::marker::Copy; - -#[derive(Default, Copy, Clone, Debug)] -pub struct FieldElement(pub [i32; 10]); - -const ZERO: FieldElement = FieldElement([0; 10]); - -impl FieldElement { - pub fn fe_zero(&mut self) { - self.0.copy_from_slice(&ZERO.0); - } - - pub fn fe_one(&mut self) { - self.fe_zero(); - self.0[0] = 1; - } - - pub fn fe_add(&mut self, a: &FieldElement, b: &FieldElement) { - self.0[0] = a.0[0] + b.0[0]; - self.0[1] = a.0[1] + b.0[1]; - self.0[2] = a.0[2] + b.0[2]; - self.0[3] = a.0[3] + b.0[3]; - self.0[4] = a.0[4] + b.0[4]; - self.0[5] = a.0[5] + b.0[5]; - self.0[6] = a.0[6] + b.0[6]; - self.0[7] = a.0[7] + b.0[7]; - self.0[8] = a.0[8] + b.0[8]; - self.0[9] = a.0[9] + b.0[9]; - } - - pub fn fe_sub(&mut self, a: &FieldElement, b: &FieldElement) { - self.0[0] = a.0[0] - b.0[0]; - self.0[1] = a.0[1] - b.0[1]; - self.0[2] = a.0[2] - b.0[2]; - self.0[3] = a.0[3] - b.0[3]; - self.0[4] = a.0[4] - b.0[4]; - self.0[5] = a.0[5] - b.0[5]; - self.0[6] = a.0[6] - b.0[6]; - self.0[7] = a.0[7] - b.0[7]; - self.0[8] = a.0[8] - b.0[8]; - self.0[9] = a.0[9] - b.0[9]; - } - - fn fe_square_internal(&self) -> (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64) { - let f0 = self.0[0] as i64; - let f1 = self.0[1] as i64; - let f2 = self.0[2] as i64; - let f3 = self.0[3] as i64; - let f4 = self.0[4] as i64; - let f5 = self.0[5] as i64; - let f6 = self.0[6] as i64; - let f7 = self.0[7] as i64; - let f8 = self.0[8] as i64; - let f9 = self.0[9] as i64; - let f0_2 = (2 * self.0[0]) as i64; - let f1_2 = (2 * self.0[1]) as i64; - let f2_2 = (2 * self.0[2]) as i64; - let f3_2 = (2 * self.0[3]) as i64; - let f4_2 = (2 * self.0[4]) as i64; - let f5_2 = (2 * self.0[5]) as i64; - let f6_2 = (2 * self.0[6]) as i64; - let f7_2 = (2 * self.0[7]) as i64; - let f5_38 = 38 * f5; // 1.31*2^30 - let f6_19 = 19 * f6; // 1.31*2^30 - let f7_38 = 38 * f7; // 1.31*2^30 - let f8_19 = 19 * f8; // 1.31*2^30 - let f9_38 = 38 * f9; // 1.31*2^30 - - let h0 = f0 * f0 + f1_2 * f9_38 + f2_2 * f8_19 + f3_2 * f7_38 + f4_2 * f6_19 + f5 * f5_38; - let h1 = f0_2 * f1 + f2 * f9_38 + f3_2 * f8_19 + f4 * f7_38 + f5_2 * f6_19; - let h2 = f0_2 * f2 + f1_2 * f1 + f3_2 * f9_38 + f4_2 * f8_19 + f5_2 * f7_38 + f6 * f6_19; - let h3 = f0_2 * f3 + f1_2 * f2 + f4 * f9_38 + f5_2 * f8_19 + f6 * f7_38; - let h4 = f0_2 * f4 + f1_2 * f3_2 + f2 * f2 + f5_2 * f9_38 + f6_2 * f8_19 + f7 * f7_38; - let h5 = f0_2 * f5 + f1_2 * f4 + f2_2 * f3 + f6 * f9_38 + f7_2 * f8_19; - let h6 = f0_2 * f6 + f1_2 * f5_2 + f2_2 * f4 + f3_2 * f3 + f7_2 * f9_38 + f8 * f8_19; - let h7 = f0_2 * f7 + f1_2 * f6 + f2_2 * f5 + f3_2 * f4 + f8 * f9_38; - let h8 = f0_2 * f8 + f1_2 * f7_2 + f2_2 * f6 + f3_2 * f5_2 + f4 * f4 + f9 * f9_38; - let h9 = f0_2 * f9 + f1_2 * f8 + f2_2 * f7 + f3_2 * f6 + f4_2 * f5; - - (h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) - } - - // fe_square sets h = 2 * f * f - // - // Can overlap h with f. - // - // Preconditions: - // |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. - // - // Postconditions: - // |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. - // See fe_mul.c for discussion of implementation strategy. - pub fn fe_square(&mut self, f: &FieldElement) { - let (h0, h1, h2, h3, h4, h5, h6, h7, h8, h9) = f.fe_square_internal(); - self.fe_combine(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); - } - - pub fn fe_square_2(&mut self, f: &FieldElement) { - let (mut h0, mut h1, mut h2, mut h3, mut h4, mut h5, mut h6, mut h7, mut h8, mut h9) = - f.fe_square_internal(); - - h0 += h0; - h1 += h1; - h2 += h2; - h3 += h3; - h4 += h4; - h5 += h5; - h6 += h6; - h7 += h7; - h8 += h8; - h9 += h9; - - self.fe_combine(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); - } - - pub fn fe_copy(&mut self, src: &FieldElement) { - self.0.copy_from_slice(&src.0); - } - - pub fn fe_cmove(&mut self, g: &FieldElement, b: i32) { - let b = -b; - self.0[0] ^= b & (self.0[0] ^ g.0[0]); - self.0[1] ^= b & (self.0[1] ^ g.0[1]); - self.0[2] ^= b & (self.0[2] ^ g.0[2]); - self.0[3] ^= b & (self.0[3] ^ g.0[3]); - self.0[4] ^= b & (self.0[4] ^ g.0[4]); - self.0[5] ^= b & (self.0[5] ^ g.0[5]); - self.0[6] ^= b & (self.0[6] ^ g.0[6]); - self.0[7] ^= b & (self.0[7] ^ g.0[7]); - self.0[8] ^= b & (self.0[8] ^ g.0[8]); - self.0[9] ^= b & (self.0[9] ^ g.0[9]); - } - - // FeNeg sets h = -f - // - // Preconditions: - // |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - // - // Postconditions: - // |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - pub fn fe_neg(&mut self, f: &FieldElement) { - self.0[0] = -f.0[0]; - self.0[1] = -f.0[1]; - self.0[2] = -f.0[2]; - self.0[3] = -f.0[3]; - self.0[4] = -f.0[4]; - self.0[5] = -f.0[5]; - self.0[6] = -f.0[6]; - self.0[7] = -f.0[7]; - self.0[8] = -f.0[8]; - self.0[9] = -f.0[9]; - } - - #[allow(clippy::too_many_arguments)] - pub fn fe_combine( - &mut self, - h0: i64, - h1: i64, - h2: i64, - h3: i64, - h4: i64, - h5: i64, - h6: i64, - h7: i64, - h8: i64, - h9: i64, - ) { - let mut h0 = h0; - let mut h1 = h1; - let mut h2 = h2; - let mut h3 = h3; - let mut h4 = h4; - let mut h5 = h5; - let mut h6 = h6; - let mut h7 = h7; - let mut h8 = h8; - let mut h9 = h9; - - /* - |h0| <= (1.1*1.1*2^52*(1+19+19+19+19)+1.1*1.1*2^50*(38+38+38+38+38)) - i.e. |h0| <= 1.2*2^59; narrower ranges for h2, h4, h6, h8 - |h1| <= (1.1*1.1*2^51*(1+1+19+19+19+19+19+19+19+19)) - i.e. |h1| <= 1.5*2^58; narrower ranges for h3, h5, h7, h9 - */ - - let mut c0 = (h0 + (1 << 25)) >> 26; - h1 += c0; - h0 -= c0 << 26; - let c4 = (h4 + (1 << 25)) >> 26; - h5 += c4; - h4 -= c4 << 26; - /* |h0| <= 2^25 */ - /* |h4| <= 2^25 */ - /* |h1| <= 1.51*2^58 */ - /* |h5| <= 1.51*2^58 */ - - let c1 = (h1 + (1 << 24)) >> 25; - h2 += c1; - h1 -= c1 << 25; - let c5 = (h5 + (1 << 24)) >> 25; - h6 += c5; - h5 -= c5 << 25; - /* |h1| <= 2^24; from now on fits into int32 */ - /* |h5| <= 2^24; from now on fits into int32 */ - /* |h2| <= 1.21*2^59 */ - /* |h6| <= 1.21*2^59 */ - - let c2 = (h2 + (1 << 25)) >> 26; - h3 += c2; - h2 -= c2 << 26; - let c6 = (h6 + (1 << 25)) >> 26; - h7 += c6; - h6 -= c6 << 26; - /* |h2| <= 2^25; from now on fits into int32 unchanged */ - /* |h6| <= 2^25; from now on fits into int32 unchanged */ - /* |h3| <= 1.51*2^58 */ - /* |h7| <= 1.51*2^58 */ - - let c3 = (h3 + (1 << 24)) >> 25; - h4 += c3; - h3 -= c3 << 25; - let c7 = (h7 + (1 << 24)) >> 25; - h8 += c7; - h7 -= c7 << 25; - /* |h3| <= 2^24; from now on fits into int32 unchanged */ - /* |h7| <= 2^24; from now on fits into int32 unchanged */ - /* |h4| <= 1.52*2^33 */ - /* |h8| <= 1.52*2^33 */ - - let c4 = (h4 + (1 << 25)) >> 26; - h5 += c4; - h4 -= c4 << 26; - let c8 = (h8 + (1 << 25)) >> 26; - h9 += c8; - h8 -= c8 << 26; - /* |h4| <= 2^25; from now on fits into int32 unchanged */ - /* |h8| <= 2^25; from now on fits into int32 unchanged */ - /* |h5| <= 1.01*2^24 */ - /* |h9| <= 1.51*2^58 */ - - let c9 = (h9 + (1 << 24)) >> 25; - h0 += c9 * 19; - h9 -= c9 << 25; - /* |h9| <= 2^24; from now on fits into int32 unchanged */ - /* |h0| <= 1.8*2^37 */ - - c0 = (h0 + (1 << 25)) >> 26; - h1 += c0; - h0 -= c0 << 26; - /* |h0| <= 2^25; from now on fits into int32 unchanged */ - /* |h1| <= 1.01*2^24 */ - - self.0[0] = h0 as i32; - self.0[1] = h1 as i32; - self.0[2] = h2 as i32; - self.0[3] = h3 as i32; - self.0[4] = h4 as i32; - self.0[5] = h5 as i32; - self.0[6] = h6 as i32; - self.0[7] = h7 as i32; - self.0[8] = h8 as i32; - self.0[9] = h9 as i32; - } - - // FeMul calculates h = f * g - // Can overlap h with f or g. - // - // Preconditions: - // |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - // |g| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - // - // Postconditions: - // |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - // - // Notes on implementation strategy: - // - // Using schoolbook multiplication. - // Karatsuba would save a little in some cost models. - // - // Most multiplications by 2 and 19 are 32-bit precomputations; - // cheaper than 64-bit postcomputations. - // - // There is one remaining multiplication by 19 in the carry chain; - // one *19 precomputation can be merged into this, - // but the resulting data flow is considerably less clean. - // - // There are 12 carries below. - // 10 of them are 2-way parallelizable and vectorizable. - // Can get away with 11 carries, but then data flow is much deeper. - // - // With tighter constraints on inputs, can squeeze carries into int32. - pub fn fe_mul(&mut self, f: &FieldElement, g: &FieldElement) { - let f0 = f.0[0] as i64; - let f1 = f.0[1] as i64; - let f2 = f.0[2] as i64; - let f3 = f.0[3] as i64; - let f4 = f.0[4] as i64; - let f5 = f.0[5] as i64; - let f6 = f.0[6] as i64; - let f7 = f.0[7] as i64; - let f8 = f.0[8] as i64; - let f9 = f.0[9] as i64; - - let f1_2 = (2 * f.0[1]) as i64; - let f3_2 = (2 * f.0[3]) as i64; - let f5_2 = (2 * f.0[5]) as i64; - let f7_2 = (2 * f.0[7]) as i64; - let f9_2 = (2 * f.0[9]) as i64; - - let g0 = g.0[0] as i64; - let g1 = g.0[1] as i64; - let g2 = g.0[2] as i64; - let g3 = g.0[3] as i64; - let g4 = g.0[4] as i64; - let g5 = g.0[5] as i64; - let g6 = g.0[6] as i64; - let g7 = g.0[7] as i64; - let g8 = g.0[8] as i64; - let g9 = g.0[9] as i64; - - let g1_19 = (19 * g.0[1]) as i64; /* 1.4*2^29 */ - let g2_19 = (19 * g.0[2]) as i64; /* 1.4*2^30; still ok */ - let g3_19 = (19 * g.0[3]) as i64; - let g4_19 = (19 * g.0[4]) as i64; - let g5_19 = (19 * g.0[5]) as i64; - let g6_19 = (19 * g.0[6]) as i64; - let g7_19 = (19 * g.0[7]) as i64; - let g8_19 = (19 * g.0[8]) as i64; - let g9_19 = (19 * g.0[9]) as i64; - - let h0 = f0 * g0 - + f1_2 * g9_19 - + f2 * g8_19 - + f3_2 * g7_19 - + f4 * g6_19 - + f5_2 * g5_19 - + f6 * g4_19 - + f7_2 * g3_19 - + f8 * g2_19 - + f9_2 * g1_19; - let h1 = f0 * g1 - + f1 * g0 - + f2 * g9_19 - + f3 * g8_19 - + f4 * g7_19 - + f5 * g6_19 - + f6 * g5_19 - + f7 * g4_19 - + f8 * g3_19 - + f9 * g2_19; - let h2 = f0 * g2 - + f1_2 * g1 - + f2 * g0 - + f3_2 * g9_19 - + f4 * g8_19 - + f5_2 * g7_19 - + f6 * g6_19 - + f7_2 * g5_19 - + f8 * g4_19 - + f9_2 * g3_19; - let h3 = f0 * g3 - + f1 * g2 - + f2 * g1 - + f3 * g0 - + f4 * g9_19 - + f5 * g8_19 - + f6 * g7_19 - + f7 * g6_19 - + f8 * g5_19 - + f9 * g4_19; - let h4 = f0 * g4 - + f1_2 * g3 - + f2 * g2 - + f3_2 * g1 - + f4 * g0 - + f5_2 * g9_19 - + f6 * g8_19 - + f7_2 * g7_19 - + f8 * g6_19 - + f9_2 * g5_19; - let h5 = f0 * g5 - + f1 * g4 - + f2 * g3 - + f3 * g2 - + f4 * g1 - + f5 * g0 - + f6 * g9_19 - + f7 * g8_19 - + f8 * g7_19 - + f9 * g6_19; - let h6 = f0 * g6 - + f1_2 * g5 - + f2 * g4 - + f3_2 * g3 - + f4 * g2 - + f5_2 * g1 - + f6 * g0 - + f7_2 * g9_19 - + f8 * g8_19 - + f9_2 * g7_19; - let h7 = f0 * g7 - + f1 * g6 - + f2 * g5 - + f3 * g4 - + f4 * g3 - + f5 * g2 - + f6 * g1 - + f7 * g0 - + f8 * g9_19 - + f9 * g8_19; - let h8 = f0 * g8 - + f1_2 * g7 - + f2 * g6 - + f3_2 * g5 - + f4 * g4 - + f5_2 * g3 - + f6 * g2 - + f7_2 * g1 - + f8 * g0 - + f9_2 * g9_19; - let h9 = f0 * g9 - + f1 * g8 - + f2 * g7 - + f3 * g6 - + f4 * g5 - + f5 * g4 - + f6 * g3 - + f7 * g2 - + f8 * g1 - + f9 * g0; - - self.fe_combine(h0, h1, h2, h3, h4, h5, h6, h7, h8, h9); - } - - pub fn fe_invert(&mut self, z: &FieldElement) { - let mut t0 = FieldElement::default(); - let mut t1 = FieldElement::default(); - let mut t2 = FieldElement::default(); - let mut t3 = FieldElement::default(); - - t0.fe_square(z); // 2^1 - t1.fe_square(&t0); // 2^2 - - for _ in 1..2 { - // 2^3 - t1.fe_square(&t1.clone()); - } - - t1.fe_mul(z, &t1.clone()); // 2^3 + 2^0 - t0.fe_mul(&t0.clone(), &t1); // 2^3 + 2^1 + 2^0 - t2.fe_square(&t0); // 2^4 + 2^2 + 2^1 - t1.fe_mul(&t1.clone(), &t2); // 2^4 + 2^3 + 2^2 + 2^1 + 2^0 - t2.fe_square(&t1); // 5,4,3,2,1 - - for _ in 1..5 { - // 9,8,7,6,5 - t2.fe_square(&t2.clone()); - } - - t1.fe_mul(&t2, &t1.clone()); // 9,8,7,6,5,4,3,2,1,0 - t2.fe_square(&t1); // 10..1 - - for _ in 1..10 { - // 19..10 - t2.fe_square(&t2.clone()); - } - - t2.fe_mul(&t2.clone(), &t1); // 19..0 - t3.fe_square(&t2); // 20..1 - - for _ in 1..20 { - // 39..20 - t3.fe_square(&t3.clone()); - } - - t2.fe_mul(&t3, &t2.clone()); // 39..0 - t2.fe_square(&t2.clone()); // 40..1 - - for _ in 1..10 { - // 49..10 - t2.fe_square(&t2.clone()); - } - - t1.fe_mul(&t2, &t1.clone()); // 49..0 - t2.fe_square(&t1); // 50..1 - - for _ in 1..50 { - // 99..50 - t2.fe_square(&t2.clone()); - } - - t2.fe_mul(&t2.clone(), &t1); // 99..0 - t3.fe_square(&t2); // 100..1 - - for _ in 1..100 { - // 199..100 - t3.fe_square(&t3.clone()); - } - - t2.fe_mul(&t3, &t2.clone()); // 199..0 - t2.fe_square(&t2.clone()); // 200..1 - - for _ in 1..50 { - // 249..50 - t2.fe_square(&t2.clone()); - } - - t1.fe_mul(&t2, &t1.clone()); // 249..0 - t1.fe_square(&t1.clone()); // 250..1 - - for _ in 1..5 { - // 254..5 - t1.fe_square(&t1.clone()); - } - - self.fe_mul(&t1, &t0); // 254..5,3,1,0 - } - - // to_bytes marshals h to s. - // Preconditions: - // |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - // - // Write p=2^255-19; q=floor(h/p). - // Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). - // - // Proof: - // Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. - // Also have |h-2^230 h9|<2^230 so |19 2^(-255)(h-2^230 h9)|<1/4. - // - // Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). - // Then 0 [u8; 32] { - let mut carry = [0i32; 10]; - - let mut q = (19 * self.0[9] + (1 << 24)) >> 25; - q = (self.0[0] + q) >> 26; - q = (self.0[1] + q) >> 25; - q = (self.0[2] + q) >> 26; - q = (self.0[3] + q) >> 25; - q = (self.0[4] + q) >> 26; - q = (self.0[5] + q) >> 25; - q = (self.0[6] + q) >> 26; - q = (self.0[7] + q) >> 25; - q = (self.0[8] + q) >> 26; - q = (self.0[9] + q) >> 25; - - // Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. - self.0[0] += 19 * q; - // Goal: Output h-2^255 q, which is between 0 and 2^255-20. - - carry[0] = self.0[0] >> 26; - self.0[1] += carry[0]; - self.0[0] -= carry[0] << 26; - carry[1] = self.0[1] >> 25; - self.0[2] += carry[1]; - self.0[1] -= carry[1] << 25; - carry[2] = self.0[2] >> 26; - self.0[3] += carry[2]; - self.0[2] -= carry[2] << 26; - carry[3] = self.0[3] >> 25; - self.0[4] += carry[3]; - self.0[3] -= carry[3] << 25; - carry[4] = self.0[4] >> 26; - self.0[5] += carry[4]; - self.0[4] -= carry[4] << 26; - carry[5] = self.0[5] >> 25; - self.0[6] += carry[5]; - self.0[5] -= carry[5] << 25; - carry[6] = self.0[6] >> 26; - self.0[7] += carry[6]; - self.0[6] -= carry[6] << 26; - carry[7] = self.0[7] >> 25; - self.0[8] += carry[7]; - self.0[7] -= carry[7] << 25; - carry[8] = self.0[8] >> 26; - self.0[9] += carry[8]; - self.0[8] -= carry[8] << 26; - carry[9] = self.0[9] >> 25; - self.0[9] -= carry[9] << 25; - // h10 = carry9 - - // Goal: Output h[0]+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. - // Have h[0]+...+2^230 h[9] between 0 and 2^255-1; - // evidently 2^255 h10-2^255 q = 0. - // Goal: Output h[0]+...+2^230 h[9]. - - [ - (self.0[0] >> 0) as u8, - (self.0[0] >> 8) as u8, - (self.0[0] >> 16) as u8, - ((self.0[0] >> 24) | (self.0[1] << 2)) as u8, - (self.0[1] >> 6) as u8, - (self.0[1] >> 14) as u8, - ((self.0[1] >> 22) | (self.0[2] << 3)) as u8, - (self.0[2] >> 5) as u8, - (self.0[2] >> 13) as u8, - ((self.0[2] >> 21) | (self.0[3] << 5)) as u8, - (self.0[3] >> 3) as u8, - (self.0[3] >> 11) as u8, - ((self.0[3] >> 19) | (self.0[4] << 6)) as u8, - (self.0[4] >> 2) as u8, - (self.0[4] >> 10) as u8, - (self.0[4] >> 18) as u8, - (self.0[5] >> 0) as u8, - (self.0[5] >> 8) as u8, - (self.0[5] >> 16) as u8, - ((self.0[5] >> 24) | (self.0[6] << 1)) as u8, - (self.0[6] >> 7) as u8, - (self.0[6] >> 15) as u8, - ((self.0[6] >> 23) | (self.0[7] << 3)) as u8, - (self.0[7] >> 5) as u8, - (self.0[7] >> 13) as u8, - ((self.0[7] >> 21) | (self.0[8] << 4)) as u8, - (self.0[8] >> 4) as u8, - (self.0[8] >> 12) as u8, - ((self.0[8] >> 20) | (self.0[9] << 6)) as u8, - (self.0[9] >> 2) as u8, - (self.0[9] >> 10) as u8, - (self.0[9] >> 18) as u8, - ] - } - - pub fn fe_is_negative(&mut self) -> u8 { - let s = self.to_bytes(); - s[0] & 1 - } -} diff --git a/sdk/core/src/crypto/edwards25519/pre_computed_group_element.rs b/sdk/core/src/crypto/edwards25519/pre_computed_group_element.rs deleted file mode 100644 index 88da9d3f03..0000000000 --- a/sdk/core/src/crypto/edwards25519/pre_computed_group_element.rs +++ /dev/null @@ -1,46 +0,0 @@ -use super::{constant::BASE, field_element::FieldElement}; -use std::marker::Copy; - -#[derive(Default, Copy, Clone, Debug)] -pub struct PreComputedGroupElement { - pub y_plus_x: FieldElement, - pub y_minus_x: FieldElement, - pub xy2d: FieldElement, -} - -impl PreComputedGroupElement { - pub fn zero(&mut self) { - self.y_plus_x.fe_one(); - self.y_minus_x.fe_one(); - self.xy2d.fe_zero(); - } - - pub fn cmove(&mut self, u: &PreComputedGroupElement, b: i32) { - self.y_plus_x.fe_cmove(&u.y_plus_x, b); - self.y_minus_x.fe_cmove(&u.y_minus_x, b); - self.xy2d.fe_cmove(&u.xy2d, b); - } - - pub fn select_point(&mut self, pos: i32, b: i32) { - let mut minus_t = PreComputedGroupElement::default(); - let b_negative = (b >> 31) & 1; - let b_abs = b - (((-b_negative) & b) << 1); - - self.zero(); - - for i in 0..8 { - self.cmove(&BASE[pos as usize][i as usize], equal(b_abs, i + 1)); - } - - minus_t.y_plus_x.fe_copy(&self.y_minus_x); - minus_t.y_minus_x.fe_copy(&self.y_plus_x); - minus_t.xy2d.fe_neg(&self.xy2d); - self.cmove(&minus_t, b_negative); - } -} - -// equal returns 1 if b == c and 0 otherwise, assuming that b and c are -// non-negative. -fn equal(b: i32, c: i32) -> i32 { - if b == c { 1 } else { 0 } -} diff --git a/sdk/core/src/crypto/edwards25519/projective_group_element.rs b/sdk/core/src/crypto/edwards25519/projective_group_element.rs deleted file mode 100644 index 1f2a18f7fc..0000000000 --- a/sdk/core/src/crypto/edwards25519/projective_group_element.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::{completed_group_element::CompletedGroupElement, field_element::FieldElement}; - -#[derive(Default)] -pub struct ProjectiveGroupElement { - pub x: FieldElement, - pub y: FieldElement, - pub z: FieldElement, -} - -impl ProjectiveGroupElement { - pub fn double(&self, r: &mut CompletedGroupElement) { - let mut t0 = FieldElement::default(); - - r.x.fe_square(&self.x); - r.z.fe_square(&self.y); - r.t.fe_square_2(&self.z); - r.y.fe_add(&self.x, &self.y); - t0.fe_square(&r.y); - r.y.fe_add(&r.z, &r.x); - r.z.fe_sub(&r.z.clone(), &r.x); - r.x.fe_sub(&t0, &r.y); - r.t.fe_sub(&r.t.clone(), &r.z.clone()); - } -} diff --git a/sdk/core/src/crypto/private_key.rs b/sdk/core/src/crypto/private_key.rs deleted file mode 100644 index 77b00c0eef..0000000000 --- a/sdk/core/src/crypto/private_key.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::fmt::Display; - -use super::edwards25519::{sc_mul_add, sc_reduce}; -use crate::crypto::edwards25519::extended_group_element::ExtendedGroupElement; -use anyhow::{Result, anyhow}; -use serde::{ - de::{Deserialize, Deserializer}, - ser::{Serialize, Serializer}, -}; -use sha2::{Digest, Sha512}; - -pub const PRIVATE_KEY_LENGTH: usize = 64; -pub const SIGNATURE_LENGTH: usize = 64; -pub const SEED_LENGTH: usize = 32; - -#[derive(Copy, Clone, Debug)] -pub struct PrivateKey(pub [u8; PRIVATE_KEY_LENGTH]); - -impl PrivateKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - match bytes.len() { - SEED_LENGTH => { - let mut h: Sha512 = Sha512::new(); - let mut hash: [u8; 64] = [0u8; 64]; - let mut digest: [u8; 32] = [0u8; 32]; - - h.update(bytes); - hash.copy_from_slice(h.finalize().as_ref()); - - digest.copy_from_slice(&hash[..32]); - - digest[0] &= 248; - digest[31] &= 127; - digest[31] |= 64; - - let mut a = ExtendedGroupElement::default(); - a.ge_scalar_mult_base(digest); - let public_key_bytes = a.to_bytes(); - - let merge: Vec = [bytes.to_vec(), public_key_bytes.to_vec()] - .concat() - .into_iter() - .collect(); - let mut bits: [u8; 64] = [0u8; 64]; - bits.copy_from_slice(&merge[..64]); - - Ok(PrivateKey(bits)) - } - PRIVATE_KEY_LENGTH => { - let mut bits: [u8; 64] = [0u8; 64]; - bits.copy_from_slice(&bytes[..64]); - - Ok(PrivateKey(bits)) - } - _ => Err(anyhow!("Invalid secret key length")), - } - } - - pub fn from_hex_str(pk: &str) -> Result { - let bytes = hex::decode(pk)?; - PrivateKey::from_bytes(bytes.as_slice()) - } - - /// Currently not in use. - /// - /// Guarded by feature "wallet-full", to avoid unnecessarily importing `rand`. - #[cfg(feature = "wallet-full")] - pub fn generate(r: &mut T) -> PrivateKey - where - T: rand::CryptoRng + rand::Rng, - { - let mut secret_key = PrivateKey([0u8; 64]); - - r.fill_bytes(&mut secret_key.0); - - secret_key - } - - pub fn to_bytes(&self) -> [u8; PRIVATE_KEY_LENGTH] { - self.0 - } - - pub fn as_bytes(&self) -> &[u8; PRIVATE_KEY_LENGTH] { - &self.0 - } - - pub fn sign(&self, message: Vec) -> [u8; 64] { - let mut h: Sha512 = Sha512::new(); - h.update(&self.0[..32]); - - let mut digest1 = [0u8; 64]; - let mut message_digest = [0u8; 64]; - let mut hram_digest = [0u8; 64]; - let mut expanded_secret_key = [0u8; 32]; - - digest1.copy_from_slice(h.finalize_reset().as_ref()); - expanded_secret_key.copy_from_slice(&digest1[..32]); - expanded_secret_key[0] &= 248; - expanded_secret_key[31] &= 63; - expanded_secret_key[31] |= 64; - - h.update(&digest1[32..]); - h.update(&message); - message_digest.copy_from_slice(h.finalize_reset().as_ref()); - - let message_digest_reduced = sc_reduce(message_digest); - let mut r = ExtendedGroupElement::default(); - r.ge_scalar_mult_base(message_digest_reduced); - - let encoded_r = r.to_bytes(); - - h.update(encoded_r); - h.update(&self.0[32..]); - h.update(&message); - hram_digest.copy_from_slice(h.finalize_reset().as_ref()); - - let hram_digest_reduced = sc_reduce(hram_digest); - - let s = sc_mul_add( - hram_digest_reduced, - expanded_secret_key, - message_digest_reduced, - ); - - let mut signature = [0u8; 64]; - - signature[..32].copy_from_slice(&encoded_r); - signature[32..].copy_from_slice(&s); - - signature - } -} - -impl Display for PrivateKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - hex::encode(&self.0[..32]).fmt(f) - } -} - -impl Serialize for PrivateKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_str()) - } -} - -impl<'de> Deserialize<'de> for PrivateKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ok(Self::from_hex_str(s.as_str()).unwrap()) - } -} diff --git a/sdk/core/src/crypto/public_key.rs b/sdk/core/src/crypto/public_key.rs deleted file mode 100644 index 6ad8604ecb..0000000000 --- a/sdk/core/src/crypto/public_key.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::fmt::Display; - -use super::private_key::PrivateKey; -use anyhow::Result; -use multiversx_chain_core::types::Address; -use serde::{ - de::{Deserialize, Deserializer}, - ser::{Serialize, Serializer}, -}; - -pub const PUBLIC_KEY_LENGTH: usize = 32; - -#[derive(Copy, Clone, Debug)] -pub struct PublicKey([u8; PUBLIC_KEY_LENGTH]); - -impl PublicKey { - pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0 - } - - pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] { - &self.0 - } - - pub fn to_address(&self) -> Address { - self.0.into() - } - - pub fn from_hex_str(pk: &str) -> Result { - let bytes = hex::decode(pk)?; - let mut bits: [u8; 32] = [0u8; 32]; - bits.copy_from_slice(&bytes[32..]); - Ok(Self(bits)) - } -} - -impl From<&PrivateKey> for PublicKey { - fn from(private_key: &PrivateKey) -> PublicKey { - let bytes = private_key.to_bytes(); - - let mut bits: [u8; 32] = [0u8; 32]; - bits.copy_from_slice(&bytes[32..]); - - PublicKey(bits) - } -} - -impl Display for PublicKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - hex::encode(self.0).fmt(f) - } -} - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_str()) - } -} - -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ok(Self::from_hex_str(s.as_str()).unwrap()) - } -} diff --git a/sdk/core/src/data.rs b/sdk/core/src/data.rs index 16e6830e57..5d9c96f416 100644 --- a/sdk/core/src/data.rs +++ b/sdk/core/src/data.rs @@ -2,7 +2,6 @@ pub mod account; pub mod account_storage; pub mod esdt; pub mod hyperblock; -pub mod keystore; pub mod network_config; pub mod network_economics; pub mod network_status; diff --git a/sdk/core/src/data/keystore.rs b/sdk/core/src/data/keystore.rs deleted file mode 100644 index 31e6b6538a..0000000000 --- a/sdk/core/src/data/keystore.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const KDF_N: u32 = 4096; -pub const KDF_R: u32 = 8; -pub const KDF_P: u32 = 1; -pub const KDF_DKLEN: usize = 32; -pub const KEYSTORE_VERSION: u32 = 4; - -#[derive(Debug)] -pub enum WalletError { - InvalidPassword, - InvalidKdf, - InvalidCipher, -} - -#[derive(Debug)] -pub enum InsertPassword { - Plaintext(String), - StandardInput, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CryptoParams { - pub iv: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct KdfParams { - pub dklen: u32, - pub salt: String, - pub n: u32, - pub r: u32, - pub p: u32, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Crypto { - pub ciphertext: String, - pub cipherparams: CryptoParams, - pub cipher: String, - pub kdf: String, - pub kdfparams: KdfParams, - pub mac: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Keystore { - pub version: u32, - pub kind: String, - pub id: String, - pub address: String, - pub bech32: String, - pub crypto: Crypto, -} - -#[derive(Clone, Debug)] -pub struct DecryptionParams { - pub derived_key_first_half: Vec, - pub iv: Vec, - pub data: Vec, -} diff --git a/sdk/core/src/data/network_config.rs b/sdk/core/src/data/network_config.rs index e0158022e3..eafe9d8c8a 100644 --- a/sdk/core/src/data/network_config.rs +++ b/sdk/core/src/data/network_config.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::std::Bech32Hrp; use serde::{Deserialize, Serialize}; // NetworkConfig holds the network configuration parameters @@ -6,7 +7,7 @@ pub struct NetworkConfig { #[serde(rename = "erd_chain_id")] pub chain_id: String, #[serde(rename = "erd_address_hrp", default = "default_hrp")] - pub address_hrp: String, + pub address_hrp: Bech32Hrp, #[serde(rename = "erd_denomination")] pub denomination: i32, #[serde(rename = "erd_gas_per_data_byte")] @@ -48,6 +49,6 @@ pub struct NetworkConfigResponse { pub data: Option, } -fn default_hrp() -> String { - "erd".to_string() +fn default_hrp() -> Bech32Hrp { + Bech32Hrp::default() } diff --git a/sdk/core/src/data/transaction.rs b/sdk/core/src/data/transaction.rs index 77d0d1d406..d63f3ba1dd 100644 --- a/sdk/core/src/data/transaction.rs +++ b/sdk/core/src/data/transaction.rs @@ -7,10 +7,12 @@ mod get_transaction_response; mod log_data; mod multiple_transactions; mod simulate_gas; +mod transaction_options; mod transaction_process_status; mod transaction_request; mod transaction_response_data; mod transaction_status; +mod transaction_version; mod tx_cost; pub use api_logs::ApiLogs; @@ -22,10 +24,12 @@ pub use get_transaction_response::{GetTransactionResponse, GetTransactionRespons pub use log_data::LogData; pub use multiple_transactions::{MultipleTransactionsResponseData, ResponseMultipleTransactions}; pub use simulate_gas::{SimulateGasTransactionData, SimulateGasTransactionResponse}; +pub use transaction_options::TransactionOptions; pub use transaction_process_status::{ProcessStatusResponse, TransactionProcessStatus}; pub use transaction_request::Transaction; pub use transaction_response_data::{ResponseTransaction, TransactionResponseData}; pub use transaction_status::{ResponseTxStatus, TransactionStatus}; +pub use transaction_version::{MIN_VERSION_FOR_OPTIONS, TransactionVersion}; pub use tx_cost::{ResponseTxCost, TxCostResponseData}; #[deprecated( diff --git a/sdk/core/src/data/transaction/arg_create_transaction.rs b/sdk/core/src/data/transaction/arg_create_transaction.rs index a1b03c312d..b7e6a8e371 100644 --- a/sdk/core/src/data/transaction/arg_create_transaction.rs +++ b/sdk/core/src/data/transaction/arg_create_transaction.rs @@ -1,6 +1,8 @@ use multiversx_chain_core::std::Bech32Address; use serde::{Deserialize, Serialize}; +use crate::data::transaction::{TransactionOptions, TransactionVersion}; + // ArgCreateTransaction will hold the transaction fields #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ArgCreateTransaction { @@ -13,7 +15,7 @@ pub struct ArgCreateTransaction { pub data: Option, pub signature: String, pub chain_id: String, - pub version: u32, - pub options: u32, + pub version: TransactionVersion, + pub options: Option, pub available_balance: String, } diff --git a/sdk/core/src/data/transaction/multiple_transactions.rs b/sdk/core/src/data/transaction/multiple_transactions.rs index c6d849f696..4d79ecc781 100644 --- a/sdk/core/src/data/transaction/multiple_transactions.rs +++ b/sdk/core/src/data/transaction/multiple_transactions.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; /// Corresponds to [`MultipleTransactionsResponseData`](https://github.com/multiversx/mx-chain-proxy-go/blob/master/data/transaction.go) in mx-chain-proxy-go. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MultipleTransactionsResponseData { - #[serde(rename = "txsSent")] + #[serde(rename = "txsSent", default)] pub num_of_sent_txs: u64, #[serde(rename = "txsHashes")] pub txs_hashes: HashMap, diff --git a/sdk/core/src/data/transaction/transaction_options.rs b/sdk/core/src/data/transaction/transaction_options.rs new file mode 100644 index 0000000000..f15bc86d5f --- /dev/null +++ b/sdk/core/src/data/transaction/transaction_options.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +bitflags::bitflags! { + /// Bit-flags that modify how a transaction is signed and processed. + /// + /// | Bit | Constant | Value | Meaning | + /// |-----|------------------|-------|---------------------------------------------------| + /// | 0 | `SIGN_WITH_HASH` | `1` | Sign over keccak256 hash instead of raw JSON. | + /// | 1 | `GUARDED` | `2` | Transaction has a guardian co-signer. | + /// + /// Requires [`TransactionVersion::V2`] or higher. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub struct TransactionOptions: u32 { + /// No special options (default for version-1 transactions). + const DEFAULT = 0b0000; + + /// Sign over the keccak256 hash of the serialised transaction. + /// Required when signing with a Ledger hardware wallet. + const SIGN_WITH_HASH = 0b0001; + + /// Transaction is protected by a guardian (co-signer). + const GUARDED = 0b0010; + } +} + +impl Serialize for TransactionOptions { + fn serialize(&self, s: S) -> Result { + s.serialize_u32(self.bits()) + } +} + +impl<'de> Deserialize<'de> for TransactionOptions { + fn deserialize>(d: D) -> Result { + let bits = u32::deserialize(d)?; + Ok(TransactionOptions::from_bits_retain(bits)) + } +} + +impl TransactionOptions { + pub fn sign_with_hash(&self) -> bool { + self.contains(TransactionOptions::SIGN_WITH_HASH) + } +} diff --git a/sdk/core/src/data/transaction/transaction_request.rs b/sdk/core/src/data/transaction/transaction_request.rs index 39b5ec5c71..2a914f16c5 100644 --- a/sdk/core/src/data/transaction/transaction_request.rs +++ b/sdk/core/src/data/transaction/transaction_request.rs @@ -1,6 +1,10 @@ +use crate::wallet::WalletSignature; use multiversx_chain_core::std::Bech32Address; use serde::{Deserialize, Serialize}; +use super::transaction_options::TransactionOptions; +use super::transaction_version::TransactionVersion; + /// Represents the structure that maps and validates user input for publishing a new transaction. /// /// Corresponds to [`Transaction`](https://github.com/multiversx/mx-chain-proxy-go/blob/master/data/transaction.go) in mx-chain-proxy-go. @@ -16,16 +20,27 @@ pub struct Transaction { #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub signature: Option, + pub signature: Option, #[serde(rename = "chainID")] pub chain_id: String, - pub version: u32, - #[serde(skip_serializing_if = "is_zero", default)] - pub options: u32, + pub version: TransactionVersion, + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub relayer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub relayer_signature: Option, } -/// This is only used for serialization. -#[allow(clippy::trivially_copy_pass_by_ref)] -fn is_zero(num: &u32) -> bool { - *num == 0 +impl Transaction { + pub fn should_sign_with_hash(&self) -> bool { + if !self.version.supports_options() { + return false; + } + + self.options + .as_ref() + .map(|o| o.sign_with_hash()) + .unwrap_or(false) + } } diff --git a/sdk/core/src/data/transaction/transaction_version.rs b/sdk/core/src/data/transaction/transaction_version.rs new file mode 100644 index 0000000000..ccbf0d101a --- /dev/null +++ b/sdk/core/src/data/transaction/transaction_version.rs @@ -0,0 +1,62 @@ +use anyhow::anyhow; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Minimum transaction version required to use [`TransactionOptions`]. +pub const MIN_VERSION_FOR_OPTIONS: u32 = 2; + +/// Transaction protocol version. +/// +/// `V2` is the default and required for hash-signing and guardian features. +/// `V1` is kept for compatibility when deserialising legacy transactions. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum TransactionVersion { + V1, + #[default] + V2, +} + +impl TransactionVersion { + /// Returns the numeric value (`1` or `2`). + pub fn as_u32(self) -> u32 { + match self { + TransactionVersion::V1 => 1, + TransactionVersion::V2 => 2, + } + } + + /// Returns `true` if this version supports [`TransactionOptions`]. + pub fn supports_options(self) -> bool { + self.as_u32() >= MIN_VERSION_FOR_OPTIONS + } +} + +impl TryFrom for TransactionVersion { + type Error = anyhow::Error; + + fn try_from(v: u32) -> Result { + match v { + 1 => Ok(TransactionVersion::V1), + 2 => Ok(TransactionVersion::V2), + other => Err(anyhow!("unknown transaction version: {other}")), + } + } +} + +impl From for u32 { + fn from(v: TransactionVersion) -> Self { + v.as_u32() + } +} + +impl Serialize for TransactionVersion { + fn serialize(&self, s: S) -> Result { + s.serialize_u32(self.as_u32()) + } +} + +impl<'de> Deserialize<'de> for TransactionVersion { + fn deserialize>(d: D) -> Result { + let v = u32::deserialize(d)?; + TransactionVersion::try_from(v).map_err(serde::de::Error::custom) + } +} diff --git a/sdk/core/src/data/vm.rs b/sdk/core/src/data/vm.rs index 74a6943747..ce0f17e6b1 100644 --- a/sdk/core/src/data/vm.rs +++ b/sdk/core/src/data/vm.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::collections::HashMap; -use crate::utils::base64_decode; +use multiversx_chain_core::std::base64_decode; #[derive(Serialize_repr, Deserialize_repr, Debug, Clone)] #[repr(u8)] @@ -107,7 +107,10 @@ impl VMOutputApi { } pub fn return_data_base64_decode(&self) -> Vec> { - self.return_data().iter().map(base64_decode).collect() + self.return_data() + .iter() + .map(|d| base64_decode(d).expect("invalid base64 in return data")) + .collect() } pub fn is_ok(&self) -> bool { diff --git a/sdk/core/src/gateway/gateway_chain_simulator_add_keys.rs b/sdk/core/src/gateway/gateway_chain_simulator_add_keys.rs index 480c9a3f20..f45ab7fa73 100644 --- a/sdk/core/src/gateway/gateway_chain_simulator_add_keys.rs +++ b/sdk/core/src/gateway/gateway_chain_simulator_add_keys.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use anyhow::anyhow; -use crate::{gateway::ADD_KEYS, utils::base64_encode}; +use crate::gateway::ADD_KEYS; +use multiversx_chain_core::std::base64_encode; use super::{ GatewayRequest, GatewayRequestType, gateway_chain_simulator_blocks::GenerateBlocksResponse, diff --git a/sdk/core/src/lib.rs b/sdk/core/src/lib.rs index c16cef68a6..40402370bd 100644 --- a/sdk/core/src/lib.rs +++ b/sdk/core/src/lib.rs @@ -3,12 +3,10 @@ #![allow(clippy::collapsible_if)] #![allow(clippy::manual_is_multiple_of)] -pub mod crypto; pub mod data; pub mod gateway; pub mod retrieve_tx_on_network; pub mod test_wallets; -pub mod utils; pub mod validator; pub mod wallet; diff --git a/sdk/core/src/retrieve_tx_on_network.rs b/sdk/core/src/retrieve_tx_on_network.rs index 25bd2e6abb..66c754930d 100644 --- a/sdk/core/src/retrieve_tx_on_network.rs +++ b/sdk/core/src/retrieve_tx_on_network.rs @@ -1,11 +1,13 @@ use crate::{ data::transaction::{ApiLogs, ApiTransactionResult, Events, LogData}, gateway::{GetTxInfo, GetTxProcessStatus}, - utils::base64_encode, }; use anyhow::{Context, Result}; use log::info; -use multiversx_chain_core::{std::Bech32Address, types::ReturnCode}; +use multiversx_chain_core::{ + std::{Bech32Address, base64_encode}, + types::ReturnCode, +}; use crate::gateway::GatewayAsyncService; diff --git a/sdk/core/src/test_wallets.rs b/sdk/core/src/test_wallets.rs index 4121b73f55..3484f08b8b 100644 --- a/sdk/core/src/test_wallets.rs +++ b/sdk/core/src/test_wallets.rs @@ -14,8 +14,14 @@ const IVAN_PEM: &str = include_str!("test_wallets/ivan.pem"); const JUDY_PEM: &str = include_str!("test_wallets/judy.pem"); const MALLORY_PEM: &str = include_str!("test_wallets/mallory.pem"); const MIKE_PEM: &str = include_str!("test_wallets/mike.pem"); +const SOCRATES_PEM: &str = include_str!("test_wallets/s0crates.pem"); +const SOPHOCLES_PEM: &str = include_str!("test_wallets/s0phocles.pem"); const SOPHIE_PEM: &str = include_str!("test_wallets/s0phie.pem"); +const SILVIA_PEM: &str = include_str!("test_wallets/s1lvia.pem"); +const SIOBHAN_PEM: &str = include_str!("test_wallets/s1obhan.pem"); const SIMON_PEM: &str = include_str!("test_wallets/s1mon.pem"); +const SZIDONIA_PEM: &str = include_str!("test_wallets/s2idonia.pem"); +const SZILARD_PEM: &str = include_str!("test_wallets/s2ilard.pem"); const SZONJA_PEM: &str = include_str!("test_wallets/s2onja.pem"); const WALLETS: &[(&str, &str)] = &[ @@ -31,76 +37,102 @@ const WALLETS: &[(&str, &str)] = &[ ("judy", JUDY_PEM), ("mallory", MALLORY_PEM), ("mike", MIKE_PEM), + ("s0crates", SOCRATES_PEM), + ("socrates", SOCRATES_PEM), + ("s0phocles", SOPHOCLES_PEM), + ("sophocles", SOPHOCLES_PEM), ("sophie", SOPHIE_PEM), ("s0phie", SOPHIE_PEM), + ("siobhan", SIOBHAN_PEM), + ("s1obhan", SIOBHAN_PEM), + ("silvia", SILVIA_PEM), + ("s1lvia", SILVIA_PEM), ("simon", SIMON_PEM), ("s1mon", SIMON_PEM), + ("szidonia", SZIDONIA_PEM), + ("s2idonia", SZIDONIA_PEM), + ("szilard", SZILARD_PEM), + ("s2ilard", SZILARD_PEM), ("szonja", SZONJA_PEM), ("s2onja", SZONJA_PEM), ]; -fn test_wallet(pem_file_contents: &str) -> Wallet { - Wallet::from_pem_file_contents(pem_file_contents.to_string()).unwrap() -} - /// Test wallet. Do not use on mainnet. pub fn alice() -> Wallet { - test_wallet(ALICE_PEM) + Wallet::new_test_wallet("alice", ALICE_PEM) } /// Test wallet. Do not use on mainnet. pub fn bob() -> Wallet { - test_wallet(BOB_PEM) + Wallet::new_test_wallet("bob", BOB_PEM) } /// Test wallet. Do not use on mainnet. pub fn carol() -> Wallet { - test_wallet(CAROL_PEM) + Wallet::new_test_wallet("carol", CAROL_PEM) } /// Test wallet. Do not use on mainnet. pub fn dan() -> Wallet { - test_wallet(DAN_PEM) + Wallet::new_test_wallet("dan", DAN_PEM) } /// Test wallet. Do not use on mainnet. pub fn eve() -> Wallet { - test_wallet(EVE_PEM) + Wallet::new_test_wallet("eve", EVE_PEM) } /// Test wallet. Do not use on mainnet. pub fn frank() -> Wallet { - test_wallet(FRANK_PEM) + Wallet::new_test_wallet("frank", FRANK_PEM) } /// Test wallet. Do not use on mainnet. pub fn grace() -> Wallet { - test_wallet(GRACE_PEM) + Wallet::new_test_wallet("grace", GRACE_PEM) } /// Test wallet. Do not use on mainnet. pub fn heidi() -> Wallet { - test_wallet(HEIDI_PEM) + Wallet::new_test_wallet("heidi", HEIDI_PEM) } /// Test wallet. Do not use on mainnet. pub fn ivan() -> Wallet { - test_wallet(IVAN_PEM) + Wallet::new_test_wallet("ivan", IVAN_PEM) } /// Test wallet. Do not use on mainnet. pub fn judy() -> Wallet { - test_wallet(JUDY_PEM) + Wallet::new_test_wallet("judy", JUDY_PEM) } /// Test wallet. Do not use on mainnet. pub fn mallory() -> Wallet { - test_wallet(MALLORY_PEM) + Wallet::new_test_wallet("mallory", MALLORY_PEM) } /// Test wallet. Do not use on mainnet. pub fn mike() -> Wallet { - test_wallet(MIKE_PEM) + Wallet::new_test_wallet("mike", MIKE_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Socrates' wallet will always be in shard 0. +/// +/// Address: 0x3d55ffd949781d5f0b5eaf57a3f0797d1db2d76a759ae6df7e335302b7d90000 +pub fn socrates() -> Wallet { + Wallet::new_test_wallet("socrates", SOCRATES_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Sophocles' wallet will always be in shard 0. +/// +/// Address: 0x35e2358aa3191bcedf6eddf6e14f138765b38a704647ed691130703852620000 +pub fn sophocles() -> Wallet { + Wallet::new_test_wallet("sophocles", SOPHOCLES_PEM) } /// Test wallet. Do not use on mainnet. @@ -109,7 +141,25 @@ pub fn mike() -> Wallet { /// /// Address: 0x14af28ce7d79117f689228b1af89d16e8b0c16a3d36062a2b6eeb8fbab6c0000 pub fn sophie() -> Wallet { - test_wallet(SOPHIE_PEM) + Wallet::new_test_wallet("sophie", SOPHIE_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Siobhan's wallet will always be in shard 1. +/// +/// Address: 0xe624b62f5dcad961ceaf9ce23e56db72377ea8a8dcc7065b73089778522d0001 +pub fn siobhan() -> Wallet { + Wallet::new_test_wallet("siobhan", SIOBHAN_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Silvia's wallet will always be in shard 1. +/// +/// Address: 0x7bbaef4fae6aa454929e0038bf01da4907e7814609db6e46c9990b5ae9d30001 +pub fn silvia() -> Wallet { + Wallet::new_test_wallet("silvia", SILVIA_PEM) } /// Test wallet. Do not use on mainnet. @@ -118,7 +168,25 @@ pub fn sophie() -> Wallet { /// /// Address: 0x4b9ab2524a7d15416fb78d4d88249dc30272bd6ee1b8a07d4342c33a40a00001 pub fn simon() -> Wallet { - test_wallet(SIMON_PEM) + Wallet::new_test_wallet("simon", SIMON_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Szidonia's wallet will always be in shard 2. +/// +/// Address: 0x99337fe8455f5798fc548037c1ceea4d95d8f89ca468663877719f7d31eb0002 +pub fn szidonia() -> Wallet { + Wallet::new_test_wallet("szidonia", SZIDONIA_PEM) +} + +/// Test wallet. Do not use on mainnet. +/// +/// Szilárd's wallet will always be in shard 2. +/// +/// Address: 0xf7a7c49bb4d2f63fd82ca0859f2e01c13f320a79c0962dfdc43fcb621cde0002 +pub fn szilard() -> Wallet { + Wallet::new_test_wallet("szilard", SZILARD_PEM) } /// Test wallet. Do not use on mainnet. @@ -127,7 +195,7 @@ pub fn simon() -> Wallet { /// /// Address: 0x5ea3f378aaaa9f51cef76093b62e1041c90b415016dfa49760d7a846a8d90002 pub fn szonja() -> Wallet { - test_wallet(SZONJA_PEM) + Wallet::new_test_wallet("szonja", SZONJA_PEM) } /// Test wallets. Do not use on mainnet. @@ -143,6 +211,16 @@ pub fn for_shard(shard_id: ShardId) -> Wallet { } } +/// Returns the wallet for the named test wallet, or `None` if the name is unknown. +/// +/// For the list of valid names see [`valid_names()`]. +pub fn by_name(name: &str) -> Option { + WALLETS + .iter() + .find(|(n, _)| *n == name) + .map(|(static_name, pem)| Wallet::new_test_wallet(static_name, pem)) +} + pub fn valid_names() -> Vec<&'static str> { WALLETS.iter().map(|(n, _)| *n).collect() } diff --git a/sdk/core/src/test_wallets/s0crates.pem b/sdk/core/src/test_wallets/s0crates.pem new file mode 100644 index 0000000000..688a25aa2b --- /dev/null +++ b/sdk/core/src/test_wallets/s0crates.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1842llk2f0qw47z674at68ure05wm94m2wkdwdhm7xdfs9d7eqqqqs2ej24----- +Y2YwZWFhOWYwNjMzZmMxNDUxNGY5MmE1NjAzNjYzZjAyZmJjOTg0ODc0YTJjZmE2 +NDcwOTA3ZmY3OTBiMTdlMjNkNTVmZmQ5NDk3ODFkNWYwYjVlYWY1N2EzZjA3OTdk +MWRiMmQ3NmE3NTlhZTZkZjdlMzM1MzAyYjdkOTAwMDA= +-----END PRIVATE KEY for erd1842llk2f0qw47z674at68ure05wm94m2wkdwdhm7xdfs9d7eqqqqs2ej24----- diff --git a/sdk/core/src/test_wallets/s0phocles.pem b/sdk/core/src/test_wallets/s0phocles.pem new file mode 100644 index 0000000000..c8693b25a1 --- /dev/null +++ b/sdk/core/src/test_wallets/s0phocles.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1xh3rtz4rryduahmwmhmwzncnsajm8znsger766g3xpcrs5nzqqqq9me2a6----- +M2Y4MTczZjA0ZmUxOGZjOTk3NDA0M2UzNTIzNGY4YWM3YzBlNjAzYzE0MjljYjU5 +NGEzZjk2MjA2MGE1ZTc0YzM1ZTIzNThhYTMxOTFiY2VkZjZlZGRmNmUxNGYxMzg3 +NjViMzhhNzA0NjQ3ZWQ2OTExMzA3MDM4NTI2MjAwMDA= +-----END PRIVATE KEY for erd1xh3rtz4rryduahmwmhmwzncnsajm8znsger766g3xpcrs5nzqqqq9me2a6----- diff --git a/sdk/core/src/test_wallets/s1lvia.pem b/sdk/core/src/test_wallets/s1lvia.pem new file mode 100644 index 0000000000..5d0ae1adba --- /dev/null +++ b/sdk/core/src/test_wallets/s1lvia.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd10waw7nawd2j9fy57qqut7qw6fyr70q2xp8dku3kfny9446wnqqqsx93xxl----- +ZTNhZjA2Y2NhNzhlNDMzMzZhNDIyMGE2NTRhZmJlNTRhYTNiNDlmNDkzNGE0ODdi +ZTg2Yjc5MmQ2Y2UwZDgyNTdiYmFlZjRmYWU2YWE0NTQ5MjllMDAzOGJmMDFkYTQ5 +MDdlNzgxNDYwOWRiNmU0NmM5OTkwYjVhZTlkMzAwMDE= +-----END PRIVATE KEY for erd10waw7nawd2j9fy57qqut7qw6fyr70q2xp8dku3kfny9446wnqqqsx93xxl----- diff --git a/sdk/core/src/test_wallets/s1obhan.pem b/sdk/core/src/test_wallets/s1obhan.pem new file mode 100644 index 0000000000..32cefd8395 --- /dev/null +++ b/sdk/core/src/test_wallets/s1obhan.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1ucjtvt6aetvkrn40nn3ru4kmwgmha29gmnrsvkmnpzths53dqqqs03kssu----- +YjI4ZGMxOWQxZjgyZjNkZTdiN2RlNWYyMDIwMTYxYTQ1NjM0MWE0OWE4YjBmMjgy +OWNkNzhkNDViMmIyMDIwNWU2MjRiNjJmNWRjYWQ5NjFjZWFmOWNlMjNlNTZkYjcy +Mzc3ZWE4YThkY2M3MDY1YjczMDg5Nzc4NTIyZDAwMDE= +-----END PRIVATE KEY for erd1ucjtvt6aetvkrn40nn3ru4kmwgmha29gmnrsvkmnpzths53dqqqs03kssu----- diff --git a/sdk/core/src/test_wallets/s2idonia.pem b/sdk/core/src/test_wallets/s2idonia.pem new file mode 100644 index 0000000000..99bbf72b7e --- /dev/null +++ b/sdk/core/src/test_wallets/s2idonia.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1nyehl6z9tate3lz5sqmurnh2fk2a37yu535xvwrhwx0h6v0tqqpqmj2rw7----- +ZmFiMjVkM2MxYmU4OWM0Njc5MmI0MDUzOWU5MmQ0NjVlMWI3NmI2ZjZkOTNjMGJl +NzBkZjEyYzYzMGJkNDUzYjk5MzM3ZmU4NDU1ZjU3OThmYzU0ODAzN2MxY2VlYTRk +OTVkOGY4OWNhNDY4NjYzODc3NzE5ZjdkMzFlYjAwMDI= +-----END PRIVATE KEY for erd1nyehl6z9tate3lz5sqmurnh2fk2a37yu535xvwrhwx0h6v0tqqpqmj2rw7----- diff --git a/sdk/core/src/test_wallets/s2ilard.pem b/sdk/core/src/test_wallets/s2ilard.pem new file mode 100644 index 0000000000..34cfe722e5 --- /dev/null +++ b/sdk/core/src/test_wallets/s2ilard.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd177nufxa56tmrlkpv5zze7tspcylnyznecztzmlwy8l9ky8x7qqpqlfthht----- +YTYwN2FjZWFjMDA2ZmYxZGZmNzIyZmRmZmIxODdlMGVlZDA5YjFhNGIxZWM2MDlj +MzU2ZTk2Mjk4MmQ4MDcxZmY3YTdjNDliYjRkMmY2M2ZkODJjYTA4NTlmMmUwMWMx +M2YzMjBhNzljMDk2MmRmZGM0M2ZjYjYyMWNkZTAwMDI= +-----END PRIVATE KEY for erd177nufxa56tmrlkpv5zze7tspcylnyznecztzmlwy8l9ky8x7qqpqlfthht----- diff --git a/sdk/core/src/utils.rs b/sdk/core/src/utils.rs index d3393c8210..b757df150b 100644 --- a/sdk/core/src/utils.rs +++ b/sdk/core/src/utils.rs @@ -1,15 +1 @@ -use base64::{Engine as _, engine::general_purpose}; - -pub fn base64_decode(to_decode: T) -> Vec -where - T: AsRef<[u8]>, -{ - general_purpose::STANDARD.decode(to_decode).unwrap() -} - -pub fn base64_encode(to_encode: T) -> String -where - T: AsRef<[u8]>, -{ - general_purpose::STANDARD.encode(to_encode) -} +pub use multiversx_chain_core::std::{base64_decode, base64_encode}; diff --git a/sdk/core/src/wallet.rs b/sdk/core/src/wallet.rs index 153efe11d2..618198e5e4 100644 --- a/sdk/core/src/wallet.rs +++ b/sdk/core/src/wallet.rs @@ -1,390 +1,27 @@ -use core::str; -use std::{ - fs::{self}, - io::{self, Write}, - path::Path, -}; - -use aes::{Aes128, cipher::KeyIvInit}; -use anyhow::Result; -use bip39::Mnemonic; -use ctr::{Ctr128BE, cipher::StreamCipher}; -use hmac::{Hmac, KeyInit, Mac}; -use multiversx_chain_core::{std::Bech32Address, types::Address}; -use pbkdf2::pbkdf2; -use scrypt::{Params, scrypt}; -use serde_json::json; -use sha2::{Digest, Sha256, Sha512}; -use sha3::Keccak256; -use zeroize::Zeroize; - -use crate::{ - crypto::{ - private_key::{PRIVATE_KEY_LENGTH, PrivateKey}, - public_key::PublicKey, - }, - data::{keystore::*, transaction::Transaction}, - utils::*, -}; - -const EGLD_COIN_TYPE: u32 = 508; -const HARDENED: u32 = 0x80000000; -const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; -const KDF_SCRYPT: &str = "scrypt"; - -type HmacSha512 = Hmac; -type HmacSha256 = Hmac; - -#[derive(Copy, Clone, Debug)] -pub struct Wallet { - priv_key: PrivateKey, -} - -impl Wallet { - fn seed_from_mnemonic(mnemonic: Mnemonic, password: &str) -> [u8; 64] { - let mut salt = String::with_capacity(8 + password.len()); - salt.push_str("mnemonic"); - salt.push_str(password); - - let mut seed = [0u8; 64]; - - let _ = pbkdf2::>( - mnemonic.to_string().as_bytes(), - salt.as_bytes(), - 2048, - &mut seed, - ); - - salt.zeroize(); - - seed - } - - pub fn get_private_key_from_mnemonic( - mnemonic: Mnemonic, - account: u32, - address_index: u32, - ) -> PrivateKey { - let seed = Self::seed_from_mnemonic(mnemonic, ""); - - let serialized_key_len = 32; - let hardened_child_padding: u8 = 0; - - let mut digest = - HmacSha512::new_from_slice(b"ed25519 seed").expect("HMAC can take key of any size"); - digest.update(&seed); - let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); - let mut key = intermediary[..serialized_key_len].to_vec(); - let mut chain_code = intermediary[serialized_key_len..].to_vec(); - - for child_idx in [ - 44 | HARDENED, - EGLD_COIN_TYPE | HARDENED, - account | HARDENED, // account - HARDENED, - address_index | HARDENED, // addressIndex - ] { - let mut buff = [vec![hardened_child_padding], key.clone()].concat(); - buff.push((child_idx >> 24) as u8); - buff.push((child_idx >> 16) as u8); - buff.push((child_idx >> 8) as u8); - buff.push(child_idx as u8); - - digest = - HmacSha512::new_from_slice(&chain_code).expect("HMAC can take key of any size"); - digest.update(&buff); - let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); - key = intermediary[..serialized_key_len].to_vec(); - chain_code = intermediary[serialized_key_len..].to_vec(); - } - - PrivateKey::from_bytes(key.as_slice()).unwrap() - } - - pub fn get_wallet_keys_mnemonic(mnemonic_str: String) -> (String, String) { - let mnemonic = Mnemonic::parse(mnemonic_str.replace('\n', "")).unwrap(); - let private_key = Self::get_private_key_from_mnemonic(mnemonic, 0u32, 0u32); - let public_key = PublicKey::from(&private_key); - - let public_key_str: &str = &public_key.to_string(); - let private_key_str: &str = &private_key.to_string(); - - (private_key_str.to_string(), public_key_str.to_string()) - } - - pub fn from_private_key(priv_key: &str) -> Result { - let priv_key = PrivateKey::from_hex_str(priv_key)?; - Ok(Self { priv_key }) - } - - pub fn from_pem_file

(file_path: P) -> Result - where - P: AsRef, - { - let contents = std::fs::read_to_string(file_path)?; - Self::from_pem_file_contents(contents) - } - - pub fn from_pem_file_contents(contents: String) -> Result { - let x = pem::parse(contents)?; - let x = x.contents()[..PRIVATE_KEY_LENGTH].to_vec(); - let priv_key_str = std::str::from_utf8(x.as_slice())?; - let pri_key = PrivateKey::from_hex_str(priv_key_str)?; - Ok(Self { priv_key: pri_key }) - } - - pub fn get_shard(&self) -> u8 { - let address = self.to_address(); - let address_bytes = address.as_bytes(); - address_bytes[address_bytes.len() - 1] % 3 - } - - pub fn get_pem_decoded_content>(file: P) -> Vec { - let pem_content = fs::read_to_string(file).unwrap(); - let lines: Vec<&str> = pem_content.split('\n').collect(); - let pem_encoded_keys = format!("{}{}{}", lines[1], lines[2], lines[3]); - base64_decode(pem_encoded_keys) - } - - pub fn get_wallet_keys_pem>(file: P) -> (String, String) { - let pem_decoded_keys = Self::get_pem_decoded_content(file); - let (private_key, public_key) = pem_decoded_keys.split_at(pem_decoded_keys.len() / 2); - let private_key_str = String::from_utf8(private_key.to_vec()).unwrap(); - let public_key_str = String::from_utf8(public_key.to_vec()).unwrap(); - - (private_key_str, public_key_str) - } - - pub fn from_keystore_secret>( - file_path: P, - insert_password: InsertPassword, - ) -> Result { - let decryption_params = match insert_password { - InsertPassword::Plaintext(password) => { - Self::validate_keystore_password(&file_path, password.to_string()).unwrap_or_else( - |e| { - panic!("Error: {:?}", e); - }, - ) - } - InsertPassword::StandardInput => { - Self::validate_keystore_password(&file_path, Self::get_keystore_password()) - .unwrap_or_else(|e| { - panic!("Error: {:?}", e); - }) - } - }; - let priv_key = PrivateKey::from_hex_str( - hex::encode(Self::decrypt_secret_key(decryption_params)).as_str(), - )?; - Ok(Self { priv_key }) - } - - pub fn get_private_key_from_keystore_secret>( - file_path: P, - password: &str, - ) -> Result { - let decyption_params = Self::validate_keystore_password(file_path, password.to_string()) - .unwrap_or_else(|e| { - panic!("Error: {:?}", e); - }); - let priv_key = PrivateKey::from_hex_str( - hex::encode(Self::decrypt_secret_key(decyption_params)).as_str(), - )?; - Ok(priv_key) - } - - #[deprecated( - since = "0.54.0", - note = "Renamed to `to_address`, type changed to multiversx_chain_core::types::Address" - )] - pub fn address(&self) -> Bech32Address { - self.to_address().to_bech32_default() - } - - pub fn to_address(&self) -> Address { - PublicKey::from(&self.priv_key).to_address() - } - - pub fn sign_tx(&self, unsign_tx: &Transaction) -> [u8; 64] { - let mut unsign_tx = unsign_tx.clone(); - unsign_tx.signature = None; - - let mut tx_bytes = json!(unsign_tx).to_string().as_bytes().to_vec(); - - let should_sign_on_tx_hash = unsign_tx.version >= 2 && unsign_tx.options & 1 > 0; - if should_sign_on_tx_hash { - let mut h = Keccak256::new(); - h.update(tx_bytes); - tx_bytes = h.finalize().to_vec(); - } - - self.priv_key.sign(tx_bytes) - } - - pub fn sign_bytes(&self, data: Vec) -> [u8; 64] { - self.priv_key.sign(data) - } - - pub fn get_keystore_password() -> String { - print!("Insert password: "); - io::stdout().flush().unwrap(); - rpassword::read_password().unwrap() - } - - pub fn validate_keystore_password>( - path: P, - password: String, - ) -> Result { - let json_body = fs::read_to_string(path).unwrap(); - let keystore: Keystore = serde_json::from_str(&json_body).unwrap(); - let ciphertext = hex::decode(&keystore.crypto.ciphertext).unwrap(); - - let cipher = &keystore.crypto.cipher; - if cipher != CIPHER_ALGORITHM_AES_128_CTR { - return Err(WalletError::InvalidCipher); - } - - let iv = hex::decode(&keystore.crypto.cipherparams.iv).unwrap(); - let salt = hex::decode(&keystore.crypto.kdfparams.salt).unwrap(); - let json_mac = hex::decode(&keystore.crypto.mac).unwrap(); - - let kdf = &keystore.crypto.kdf; - if kdf != KDF_SCRYPT { - return Err(WalletError::InvalidKdf); - } - let n = keystore.crypto.kdfparams.n as f64; - let r = keystore.crypto.kdfparams.r as u64; - let p = keystore.crypto.kdfparams.p as u64; - let _dklen = keystore.crypto.kdfparams.dklen as usize; - - let params = Params::new(n.log2() as u8, r as u32, p as u32).unwrap(); - - let mut derived_key = vec![0u8; 32]; - scrypt(password.as_bytes(), &salt, ¶ms, &mut derived_key).unwrap(); - - let derived_key_first_half = derived_key[0..16].to_vec(); - let derived_key_second_half = derived_key[16..32].to_vec(); - - let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); - input_mac.update(&ciphertext); - let computed_mac = input_mac.finalize().into_bytes(); - - if computed_mac.to_vec() == json_mac { - println!("Password is correct"); - Ok(DecryptionParams { - derived_key_first_half, - iv, - data: ciphertext, - }) - } else { - println!("Password is incorrect"); - Err(WalletError::InvalidPassword) - } - } - - pub fn decrypt_secret_key(decryption_params: DecryptionParams) -> Vec { - let key: &[u8; 16] = decryption_params - .derived_key_first_half - .as_slice() - .try_into() - .unwrap(); - let iv: &[u8; 16] = decryption_params.iv.as_slice().try_into().unwrap(); - let mut cipher = Ctr128BE::::new(key.into(), iv.into()); - let mut decrypted = decryption_params.data.to_vec(); - cipher.apply_keystream(&mut decrypted); - - decrypted - } - - /// Not available in dapps, since it uses randomness to generate the keystore. - /// - /// Only available in the sc-meta standalone CLI. - #[cfg(feature = "wallet-full")] - pub fn encrypt_keystore( - data: &[u8], - hrp: &str, - address: &Address, - public_key: &str, - password: &str, - ) -> String { - use rand::Rng; - - let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P).unwrap(); - let mut rand_salt: [u8; 32] = [0u8; 32]; - rand::rng().fill_bytes(&mut rand_salt); - let salt_hex = hex::encode(rand_salt); - - let mut rand_iv: [u8; 16] = [0u8; 16]; - rand::rng().fill_bytes(&mut rand_iv); - let iv_hex = hex::encode(rand_iv); - - let mut derived_key = vec![0u8; 32]; - scrypt(password.as_bytes(), &rand_salt, ¶ms, &mut derived_key).unwrap(); - - let derived_key_first_half = derived_key[0..16].to_vec(); - let derived_key_second_half = derived_key[16..32].to_vec(); - - let decryption_params = DecryptionParams { - derived_key_first_half, - iv: rand_iv.to_vec(), - data: data.to_vec(), - }; - - let ciphertext = Self::decrypt_secret_key(decryption_params); - - let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); - h.update(&ciphertext); - let mac = h.finalize().into_bytes(); - let keystore = Keystore { - crypto: Crypto { - cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(), - cipherparams: CryptoParams { iv: iv_hex }, - ciphertext: hex::encode(&ciphertext), - kdf: KDF_SCRYPT.to_string(), - kdfparams: KdfParams { - salt: salt_hex, - n: KDF_N, - r: KDF_R, - p: KDF_P, - dklen: KDF_DKLEN as u32, - }, - mac: hex::encode(mac), - }, - id: uuid::Uuid::new_v4().to_string(), - version: KEYSTORE_VERSION, - kind: "secretKey".to_string(), - address: public_key.to_string(), - bech32: address.to_bech32(hrp).bech32, - }; - - let mut keystore_json: String = serde_json::to_string_pretty(&keystore).unwrap(); - keystore_json.push('\n'); - keystore_json - } - - pub fn generate_pem_content( - hrp: &str, - address: &Address, - private_key: &str, - public_key: &str, - ) -> String { - let concat_keys = format!("{}{}", private_key, public_key); - let concat_keys_b64 = base64_encode(concat_keys); - - // Split the base64 string into 64-character lines - let formatted_key = concat_keys_b64 - .as_bytes() - .chunks(64) - .map(|chunk| std::str::from_utf8(chunk).unwrap()) - .collect::>() - .join("\n"); - - let address_bech32 = Bech32Address::encode_address(hrp, address.clone()); - let pem_content = format!( - "-----BEGIN PRIVATE KEY for {address_bech32}-----\n{formatted_key}\n-----END PRIVATE KEY for {address_bech32}-----\n" - ); - - pem_content - } -} +mod keystore; +mod keystore_error; +mod keystore_json; +mod mnemonic; +mod private_key; +mod public_key; +mod signer; +mod wallet_impl; +mod wallet_pem; +mod wallet_signature; +mod wallet_source; + +#[cfg(feature = "ledger")] +pub mod ledger; + +pub use keystore::Keystore; +pub use keystore::KeystoreRandomness; +pub use keystore_error::KeystoreError; +pub use keystore_json::*; +pub use mnemonic::Mnemonic; +pub use private_key::{PRIVATE_KEY_LENGTH, PrivateKey, SEED_LENGTH}; +pub use public_key::{PUBLIC_KEY_LENGTH, PublicKey}; +pub use signer::Signer; +pub use wallet_impl::Wallet; +pub use wallet_pem::WalletPem; +pub use wallet_signature::WalletSignature; +pub use wallet_source::WalletSource; diff --git a/sdk/core/src/wallet/keystore.rs b/sdk/core/src/wallet/keystore.rs new file mode 100644 index 0000000000..e7b5daaa22 --- /dev/null +++ b/sdk/core/src/wallet/keystore.rs @@ -0,0 +1,188 @@ +use std::fs; +use std::path::Path; + +use aes::{Aes128, cipher::KeyIvInit}; +use ctr::{Ctr128BE, cipher::StreamCipher}; +use hmac::{Hmac, KeyInit, Mac}; +use multiversx_chain_core::std::{Bech32Address, Bech32Hrp}; +use scrypt::{Params, scrypt}; +use sha2::Sha256; + +use super::{KeystoreError, KeystoreJson, PrivateKey, PublicKey, Wallet}; + +const KDF_N: u32 = 4096; +const KDF_R: u32 = 8; +const KDF_P: u32 = 1; +const KDF_DKLEN: usize = 32; +const KEYSTORE_VERSION: u32 = 4; +const CIPHER_ALGORITHM_AES_128_CTR: &str = "aes-128-ctr"; +const KDF_SCRYPT: &str = "scrypt"; +const KIND_SECRET_KEY: &str = "secretKey"; + +type HmacSha256 = Hmac; + +/// Groups all randomness inputs required to encrypt a keystore. +/// +/// Keeping these separate from [`Keystore::encrypt`] makes the function +/// deterministic and easy to test with fixed values. +pub struct KeystoreRandomness { + pub salt: [u8; 32], + pub iv: [u8; 16], + pub id: String, +} + +/// Keystore with all fields decoded from their hex-string representation. +pub struct Keystore { + pub version: u32, + pub kind: String, + pub address: Bech32Address, + pub cipher: String, + pub ciphertext: Vec, + pub kdf: String, + pub n: u32, + pub r: u32, + pub p: u32, + pub dklen: u32, + pub mac: Vec, + pub randomness: KeystoreRandomness, +} + +impl Keystore { + pub fn from_file>(path: P) -> anyhow::Result { + let json_str = fs::read_to_string(path)?; + let json: KeystoreJson = serde_json::from_str(&json_str)?; + Self::from_json(json) + } + + pub fn to_json_string(&self) -> String { + let mut s = serde_json::to_string_pretty(&self.to_json()).unwrap(); + s.push('\n'); + s + } + + /// Decrypts the keystore with the given password and returns a [`Wallet`]. + /// + /// Returns [`KeystoreError::InvalidPassword`] if the MAC check fails, or + /// other variants for unsupported cipher / KDF parameters. + pub fn decrypt_wallet(&self, password: &str) -> Result { + if self.cipher != CIPHER_ALGORITHM_AES_128_CTR { + return Err(KeystoreError::InvalidCipher); + } + if self.kdf != KDF_SCRYPT { + return Err(KeystoreError::InvalidKdf); + } + + let n = self.n as f64; + let params = Params::new(n.log2() as u8, self.r, self.p) + .map_err(|e| KeystoreError::Other(e.into()))?; + + let mut derived_key = [0u8; 32]; + scrypt( + password.as_bytes(), + &self.randomness.salt, + ¶ms, + &mut derived_key, + ) + .unwrap(); + + let (derived_key_first_half, derived_key_second_half) = split_derived_key(&derived_key); + + let mut input_mac = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + input_mac.update(&self.ciphertext); + let computed_mac = input_mac.finalize().into_bytes(); + + if computed_mac.to_vec() != self.mac { + return Err(KeystoreError::InvalidPassword); + } + + let private_key_bytes = run_cipher( + derived_key_first_half, + self.randomness.iv, + self.ciphertext.clone(), + ); + + let private_key_arr: [u8; 64] = private_key_bytes + .try_into() + .map_err(|_| anyhow::anyhow!("decrypted keystore has wrong key length"))?; + let private_key = PrivateKey::from_keypair_bytes(&private_key_arr)?; + Ok(Wallet::new( + private_key, + super::WalletSource::Keystore(self.address.hrp), + )) + } + + /// Encrypts a private key into a keystore using scrypt + AES-128-CTR + HMAC-SHA256. + /// + /// The wallet address stored in the keystore is derived from `private_key` + /// and encoded with the given `hrp`. + pub fn encrypt( + private_key: &PrivateKey, + hrp: Bech32Hrp, + password: &str, + randomness: KeystoreRandomness, + ) -> Self { + let public_key = PublicKey::from(private_key); + let address = public_key.to_address().to_bech32(hrp); + let private_key_bytes = private_key.to_bytes(); + + let params = Params::new((KDF_N as f64).log2() as u8, KDF_R, KDF_P).unwrap(); + + let mut derived_key = [0u8; 32]; + scrypt( + password.as_bytes(), + &randomness.salt, + ¶ms, + &mut derived_key, + ) + .unwrap(); + + let (derived_key_first_half, derived_key_second_half) = split_derived_key(&derived_key); + + let ciphertext = run_cipher( + derived_key_first_half, + randomness.iv, + private_key_bytes.to_vec(), + ); + + let mut h = HmacSha256::new_from_slice(&derived_key_second_half).unwrap(); + h.update(&ciphertext); + let mac = h.finalize().into_bytes().to_vec(); + + Keystore { + version: KEYSTORE_VERSION, + kind: KIND_SECRET_KEY.to_string(), + address, + cipher: CIPHER_ALGORITHM_AES_128_CTR.to_string(), + ciphertext, + kdf: KDF_SCRYPT.to_string(), + n: KDF_N, + r: KDF_R, + p: KDF_P, + dklen: KDF_DKLEN as u32, + mac, + randomness, + } + } +} + +/// Applies AES-128-CTR to `data` in place and returns the result. +/// +/// AES-128-CTR is a symmetric stream cipher, so the same operation both +/// encrypts plaintext and decrypts ciphertext. +fn run_cipher(key: [u8; 16], iv: [u8; 16], mut data: Vec) -> Vec { + let mut cipher = Ctr128BE::::new((&key).into(), (&iv).into()); + cipher.apply_keystream(&mut data); + data +} + +/// Splits a 32-byte scrypt derived key into two 16-byte halves. +/// +/// The first half is used as the AES-128 cipher key; the second half is +/// used as the HMAC-SHA256 key for MAC verification. +fn split_derived_key(derived_key: &[u8; 32]) -> ([u8; 16], [u8; 16]) { + let mut first_half = [0u8; 16]; + let mut second_half = [0u8; 16]; + first_half.copy_from_slice(&derived_key[..16]); + second_half.copy_from_slice(&derived_key[16..]); + (first_half, second_half) +} diff --git a/sdk/core/src/wallet/keystore_error.rs b/sdk/core/src/wallet/keystore_error.rs new file mode 100644 index 0000000000..4af6470f75 --- /dev/null +++ b/sdk/core/src/wallet/keystore_error.rs @@ -0,0 +1,26 @@ +#[derive(Debug)] +pub enum KeystoreError { + InvalidPassword, + InvalidKdf, + InvalidCipher, + Other(anyhow::Error), +} + +impl std::fmt::Display for KeystoreError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + KeystoreError::InvalidPassword => write!(f, "invalid password"), + KeystoreError::InvalidKdf => write!(f, "invalid KDF"), + KeystoreError::InvalidCipher => write!(f, "invalid cipher"), + KeystoreError::Other(e) => write!(f, "{e}"), + } + } +} + +impl std::error::Error for KeystoreError {} + +impl From for KeystoreError { + fn from(e: anyhow::Error) -> Self { + KeystoreError::Other(e) + } +} diff --git a/sdk/core/src/wallet/keystore_json.rs b/sdk/core/src/wallet/keystore_json.rs new file mode 100644 index 0000000000..a3aa807e60 --- /dev/null +++ b/sdk/core/src/wallet/keystore_json.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Serialize}; + +use multiversx_chain_core::std::Bech32Address; + +use super::{Keystore, KeystoreRandomness}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CryptoParams { + pub iv: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KdfParams { + pub dklen: u32, + pub salt: String, + pub n: u32, + pub r: u32, + pub p: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Crypto { + pub ciphertext: String, + pub cipherparams: CryptoParams, + pub cipher: String, + pub kdf: String, + pub kdfparams: KdfParams, + pub mac: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeystoreJson { + pub version: u32, + pub kind: String, + pub id: String, + pub address: String, + pub bech32: String, + pub crypto: Crypto, +} + +impl Keystore { + pub fn from_json(json: KeystoreJson) -> anyhow::Result { + let ciphertext = hex::decode(&json.crypto.ciphertext)?; + let iv_bytes = hex::decode(&json.crypto.cipherparams.iv)?; + let iv: [u8; 16] = iv_bytes + .as_slice() + .try_into() + .map_err(|_: std::array::TryFromSliceError| anyhow::anyhow!("iv must be 16 bytes"))?; + let salt_bytes = hex::decode(&json.crypto.kdfparams.salt)?; + let salt: [u8; 32] = salt_bytes + .as_slice() + .try_into() + .map_err(|_: std::array::TryFromSliceError| anyhow::anyhow!("salt must be 32 bytes"))?; + let mac = hex::decode(&json.crypto.mac)?; + Ok(Keystore { + version: json.version, + kind: json.kind, + address: Bech32Address::from_bech32_str(&json.bech32), + cipher: json.crypto.cipher, + ciphertext, + kdf: json.crypto.kdf, + n: json.crypto.kdfparams.n, + r: json.crypto.kdfparams.r, + p: json.crypto.kdfparams.p, + dklen: json.crypto.kdfparams.dklen, + mac, + randomness: KeystoreRandomness { + salt, + iv, + id: json.id, + }, + }) + } + + pub fn to_json(&self) -> KeystoreJson { + KeystoreJson { + version: self.version, + kind: self.kind.clone(), + id: self.randomness.id.clone(), + address: self.address.address.to_hex(), + bech32: self.address.bech32.clone(), + crypto: Crypto { + cipher: self.cipher.clone(), + cipherparams: CryptoParams { + iv: hex::encode(self.randomness.iv), + }, + ciphertext: hex::encode(&self.ciphertext), + kdf: self.kdf.clone(), + kdfparams: KdfParams { + salt: hex::encode(self.randomness.salt), + n: self.n, + r: self.r, + p: self.p, + dklen: self.dklen, + }, + mac: hex::encode(&self.mac), + }, + } + } +} diff --git a/sdk/core/src/wallet/ledger.rs b/sdk/core/src/wallet/ledger.rs new file mode 100644 index 0000000000..38f9940703 --- /dev/null +++ b/sdk/core/src/wallet/ledger.rs @@ -0,0 +1,14 @@ +mod ledger_app; +mod ledger_config; +mod ledger_error; +mod ledger_transport; +mod ledger_transport_mock; +mod ledger_transport_native; +mod ledger_transport_wallet; + +pub use ledger_app::LedgerApp; +pub use ledger_config::LedgerAppConfiguration; +pub use ledger_error::LedgerError; +pub use ledger_transport::LedgerTransport; +pub use ledger_transport_mock::MockTransport; +pub use ledger_transport_wallet::WalletTransport; diff --git a/sdk/core/src/wallet/ledger/ledger_app.rs b/sdk/core/src/wallet/ledger/ledger_app.rs new file mode 100644 index 0000000000..9ee1070368 --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_app.rs @@ -0,0 +1,167 @@ +use ledger_apdu::APDUCommand; +use ledger_transport_hid::{TransportNativeHID, hidapi::HidApi}; + +use super::{LedgerAppConfiguration, LedgerError, LedgerTransport}; + +pub(super) const CLA: u8 = 0xED; +pub(super) const INS_GET_APP_CONFIG: u8 = 0x02; +pub(super) const INS_GET_ADDRESS: u8 = 0x03; +pub(super) const INS_SET_ADDRESS: u8 = 0x05; +pub(super) const INS_SIGN_MESSAGE: u8 = 0x06; +pub(super) const INS_SIGN_HASH_TX: u8 = 0x07; + +// Account index is always 0 for MultiversX. +const DEFAULT_ACCOUNT_INDEX: u32 = 0; + +pub(super) const MAX_CHUNK_SIZE: usize = 150; +// Ledger responses: first byte is the length prefix (0x40 = 64), followed by 64 signature bytes. +pub(super) const EXPECTED_SIG_FIRST_BYTE: u8 = 0x40; +pub(super) const SIG_RESPONSE_LEN: usize = 65; +pub(super) const SIG_LEN: usize = 64; + +/// Communicates with the MultiversX app on a connected Ledger hardware device. +/// +/// Uses dynamic dispatch over [`LedgerTransport`] so different transports +/// (real HID device, [`WalletTransport`], or a test mock) can be plugged in +/// without changing the type signature of callers. +/// +/// Only available when the `ledger` feature is enabled. +pub struct LedgerApp { + transport: Box, +} + +impl LedgerApp { + /// Opens a connection to the first available Ledger device. + /// + /// Returns [`LedgerError::DeviceNotFound`] if no device is found or if the + /// MultiversX app is not open on it. + pub fn new() -> Result { + let hidapi = HidApi::new().map_err(|e| LedgerError::Transport(e.to_string()))?; + let transport = + TransportNativeHID::new(&hidapi).map_err(|_| LedgerError::DeviceNotFound)?; + Ok(LedgerApp { + transport: Box::new(transport), + }) + } + + /// Creates a [`LedgerApp`] backed by any [`LedgerTransport`] implementation. + pub fn with_transport(transport: Box) -> Self { + LedgerApp { transport } + } + /// Returns the app version string (`"MAJOR.MINOR.PATCH"`). + pub fn get_version(&mut self) -> Result { + Ok(self.get_app_configuration()?.version) + } + + /// Returns the full app configuration (version, data flag, account/address indexes). + pub fn get_app_configuration(&mut self) -> Result { + let response = self.exchange(INS_GET_APP_CONFIG, 0x00, 0x00, &[])?; + if response.len() < 6 { + return Err(LedgerError::InvalidResponse(format!( + "GET_APP_CONFIG: expected ≥6 bytes, got {}", + response.len() + ))); + } + let data_activated = response[0] == 0x01; + let account_index = response[1]; + let address_index = response[2]; + let version = format!("{}.{}.{}", response[3], response[4], response[5]); + Ok(LedgerAppConfiguration { + data_activated, + account_index, + address_index, + version, + }) + } + + /// Returns the bech32 address for the given `address_index` (account 0). + pub fn get_address(&mut self, address_index: u32) -> Result { + let data = build_account_address_data(address_index); + let response = self.exchange(INS_GET_ADDRESS, 0x00, 0x00, &data)?; + let len = *response + .first() + .ok_or_else(|| LedgerError::InvalidResponse("empty GET_ADDRESS response".into()))? + as usize; + let address_bytes = response.get(1..1 + len).ok_or_else(|| { + LedgerError::InvalidResponse(format!( + "GET_ADDRESS: response too short for length prefix {len}" + )) + })?; + String::from_utf8(address_bytes.to_vec()) + .map_err(|e| LedgerError::InvalidResponse(format!("non-UTF8 address: {e}"))) + } + + /// Sets the active address index on the device. + pub fn set_address(&mut self, address_index: u32) -> Result<(), LedgerError> { + let data = build_account_address_data(address_index); + self.exchange(INS_SET_ADDRESS, 0x00, 0x00, &data)?; + Ok(()) + } + + /// Signs a serialised transaction (raw JSON bytes) using the hash-based + /// signing instruction (`0x07`). The device hashes the payload internally. + /// + /// Returns the 64-byte ed25519 signature. + pub fn sign_transaction(&mut self, tx_bytes: &[u8]) -> Result<[u8; SIG_LEN], LedgerError> { + self.do_sign(tx_bytes, INS_SIGN_HASH_TX) + } + + /// Signs an arbitrary message using the message signing instruction (`0x06`). + /// + /// Prepend the 4-byte big-endian length prefix before calling this function + /// (the caller is responsible, matching the Python SDK). + /// + /// Returns the 64-byte ed25519 signature. + pub fn sign_message(&mut self, message_bytes: &[u8]) -> Result<[u8; SIG_LEN], LedgerError> { + self.do_sign(message_bytes, INS_SIGN_MESSAGE) + } + + // ── Internal helpers ──────────────────────────────────────────────────── + + /// Sends `data` to the device in chunks (≤150 bytes each) using `ins` as + /// the signing instruction. Returns the 64-byte signature. + fn do_sign(&mut self, data: &[u8], ins: u8) -> Result<[u8; SIG_LEN], LedgerError> { + let chunks: Vec<&[u8]> = data.chunks(MAX_CHUNK_SIZE).collect(); + let chunk_count = chunks.len(); + + let mut last_response = Vec::new(); + for (i, chunk) in chunks.into_iter().enumerate() { + let p1: u8 = if i == 0 { 0x00 } else { 0x80 }; + last_response = self.exchange(ins, p1, 0x00, chunk)?; + } + + // After all chunks the device sends the signature: [0x40, sig[0..64]] + if last_response.len() != SIG_RESPONSE_LEN || last_response[0] != EXPECTED_SIG_FIRST_BYTE { + if chunk_count == 0 { + return Err(LedgerError::InvalidResponse("empty data to sign".into())); + } + return Err(LedgerError::InvalidResponse(format!( + "unexpected signature response length {} or first byte 0x{:02X}", + last_response.len(), + last_response.first().copied().unwrap_or(0) + ))); + } + + let mut sig = [0u8; SIG_LEN]; + sig.copy_from_slice(&last_response[1..]); + Ok(sig) + } + + fn exchange(&mut self, ins: u8, p1: u8, p2: u8, data: &[u8]) -> Result, LedgerError> { + let command = APDUCommand { + cla: CLA, + ins, + p1, + p2, + data: data.to_vec(), + }; + self.transport.exchange(&command) + } +} + +pub(super) fn build_account_address_data(address_index: u32) -> Vec { + let mut data = Vec::with_capacity(8); + data.extend_from_slice(&DEFAULT_ACCOUNT_INDEX.to_be_bytes()); + data.extend_from_slice(&address_index.to_be_bytes()); + data +} diff --git a/sdk/core/src/wallet/ledger/ledger_config.rs b/sdk/core/src/wallet/ledger/ledger_config.rs new file mode 100644 index 0000000000..d95c49003c --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_config.rs @@ -0,0 +1,9 @@ +/// Configuration returned by the `GET_APP_CONFIG` (`0x02`) instruction. +#[derive(Debug, Clone)] +pub struct LedgerAppConfiguration { + pub data_activated: bool, + pub account_index: u8, + pub address_index: u8, + /// Version string in the form `"MAJOR.MINOR.PATCH"`. + pub version: String, +} diff --git a/sdk/core/src/wallet/ledger/ledger_error.rs b/sdk/core/src/wallet/ledger/ledger_error.rs new file mode 100644 index 0000000000..708593d4c3 --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_error.rs @@ -0,0 +1,65 @@ +use std::fmt; + +/// Errors that can be returned by [`LedgerApp`](super::LedgerApp) operations. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LedgerError { + /// The device could not be found or opened (not plugged in, locked, or wrong app). + DeviceNotFound, + /// The device returned a non-success status word. + StatusWord(u16), + /// A lower-level HID transport error. + Transport(String), + /// The APDU response had an unexpected format. + InvalidResponse(String), +} + +impl LedgerError { + pub(super) fn from_status_word(sw: u16) -> Self { + LedgerError::StatusWord(sw) + } +} + +impl fmt::Display for LedgerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LedgerError::DeviceNotFound => write!( + f, + "Ledger device not found; check that it is plugged in, unlocked, and on the MultiversX app" + ), + LedgerError::StatusWord(sw) => { + write!(f, "Ledger error (0x{sw:04X}): {}", status_word_message(*sw)) + } + LedgerError::Transport(msg) => write!(f, "Ledger transport error: {msg}"), + LedgerError::InvalidResponse(msg) => write!(f, "Ledger invalid response: {msg}"), + } + } +} + +impl std::error::Error for LedgerError {} + +fn status_word_message(sw: u16) -> &'static str { + match sw { + 0x9000 => "success", + 0x6985 => "user denied", + 0x6D00 => "unknown instruction", + 0x6E00 => "wrong CLA", + 0x6E10 => "signature failed", + 0x6E01 => "invalid arguments", + 0x6E02 => "invalid message", + 0x6E03 => "invalid p1", + 0x6E04 => "message too long", + 0x6E05 => "receiver too long", + 0x6E06 => "amount too long", + 0x6E07 => "contract data disabled", + 0x6E08 => "message incomplete", + 0x6E09 => "wrong tx version", + 0x6E0A => "nonce too long", + 0x6E0B => "invalid amount", + 0x6E0C => "invalid fee", + 0x6E0D => "pretty failed", + 0x6E0E => "data too long", + 0x6E0F => "wrong tx options", + 0x6E11 => "regular signing is deprecated", + _ => "unknown error", + } +} diff --git a/sdk/core/src/wallet/ledger/ledger_transport.rs b/sdk/core/src/wallet/ledger/ledger_transport.rs new file mode 100644 index 0000000000..6dc33bf659 --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_transport.rs @@ -0,0 +1,12 @@ +use ledger_apdu::APDUCommand; + +use super::LedgerError; + +/// Abstraction over the physical transport layer. +/// +/// Implementors send a single APDU command and return the response payload +/// (already status-word-checked). The only production implementation is +/// [`TransportNativeHID`]; tests provide a [`MockTransport`]. +pub trait LedgerTransport { + fn exchange(&mut self, command: &APDUCommand>) -> Result, LedgerError>; +} diff --git a/sdk/core/src/wallet/ledger/ledger_transport_mock.rs b/sdk/core/src/wallet/ledger/ledger_transport_mock.rs new file mode 100644 index 0000000000..de2d9c226e --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_transport_mock.rs @@ -0,0 +1,263 @@ +use std::cell::RefCell; +use std::collections::VecDeque; +use std::rc::Rc; + +use ledger_apdu::APDUCommand; + +use super::{LedgerError, LedgerTransport}; + +/// A deterministic mock transport for unit testing. +/// +/// Responses are queued up at construction time and consumed one-by-one as +/// [`exchange`](LedgerTransport::exchange) is called. Exhausting the queue +/// returns a `Transport` error. +/// +/// Call [`commands_spy`](MockTransport::commands_spy) before boxing to +/// retain a handle for asserting on recorded APDUs after the transport has +/// been moved into a [`LedgerApp`]. +pub struct MockTransport { + responses: VecDeque, LedgerError>>, + commands: Rc>>>>, +} + +impl MockTransport { + pub fn new(responses: impl IntoIterator, LedgerError>>) -> Self { + MockTransport { + responses: responses.into_iter().collect(), + commands: Rc::new(RefCell::new(Vec::new())), + } + } + + /// Returns a clone of the shared command recorder for post-hoc assertion. + pub fn commands_spy(&self) -> Rc>>>> { + Rc::clone(&self.commands) + } + + /// Convenience: queue a success response with the given payload bytes. + pub fn ok(bytes: impl Into>) -> Result, LedgerError> { + Ok(bytes.into()) + } + + /// Convenience: queue a status-word error response. + pub fn err(sw: u16) -> Result, LedgerError> { + Err(LedgerError::from_status_word(sw)) + } +} + +impl LedgerTransport for MockTransport { + fn exchange(&mut self, command: &APDUCommand>) -> Result, LedgerError> { + self.commands.borrow_mut().push(command.clone()); + self.responses + .pop_front() + .unwrap_or_else(|| Err(LedgerError::Transport("mock: no more responses".into()))) + } +} + +#[cfg(test)] +mod tests { + use crate::wallet::ledger::{LedgerApp, ledger_app}; + + use super::*; + + /// Creates a valid 65-byte mock signature response for a given 64-byte + /// signature payload. + pub fn sig_response(sig: [u8; ledger_app::SIG_LEN]) -> Vec { + let mut r = vec![0x40u8]; + r.extend_from_slice(&sig); + r + } + + /// Returns a fixed 64-byte dummy signature (all `0xAB`). + pub fn dummy_sig() -> [u8; ledger_app::SIG_LEN] { + [0xABu8; ledger_app::SIG_LEN] + } + + pub fn app_with( + responses: impl IntoIterator, LedgerError>>, + ) -> LedgerApp { + LedgerApp::with_transport(Box::new(MockTransport::new(responses))) + } + + // ── get_app_configuration ──────────────────────────────────────────────── + + #[test] + fn test_get_app_configuration_parses_response() { + // Response: data_activated=true, account=0, address=0, version=1.2.5 + let mut app = app_with([MockTransport::ok(vec![0x01, 0x00, 0x00, 0x01, 0x02, 0x05])]); + let config = app.get_app_configuration().unwrap(); + assert!(config.data_activated); + assert_eq!(config.account_index, 0); + assert_eq!(config.address_index, 0); + assert_eq!(config.version, "1.2.5"); + } + + #[test] + fn test_get_app_configuration_data_not_activated() { + let mut app = app_with([MockTransport::ok(vec![0x00, 0x00, 0x00, 0x02, 0x00, 0x01])]); + let config = app.get_app_configuration().unwrap(); + assert!(!config.data_activated); + assert_eq!(config.version, "2.0.1"); + } + + #[test] + fn test_get_app_configuration_too_short_returns_error() { + let mut app = app_with([MockTransport::ok(vec![0x01, 0x00])]); + assert!(matches!( + app.get_app_configuration(), + Err(LedgerError::InvalidResponse(_)) + )); + } + + // ── get_address ────────────────────────────────────────────────────────── + + #[test] + fn test_get_address_parses_response() { + let address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + // Response: [length_byte, address_bytes...] + let mut response = vec![address.len() as u8]; + response.extend_from_slice(address.as_bytes()); + + let mut app = app_with([MockTransport::ok(response)]); + assert_eq!(app.get_address(0).unwrap(), address); + } + + #[test] + fn test_get_address_sends_correct_apdu() { + let address = "erd1abc"; + let mut response = vec![address.len() as u8]; + response.extend_from_slice(address.as_bytes()); + + let transport = MockTransport::new([MockTransport::ok(response)]); + let commands = transport.commands_spy(); + let mut app = LedgerApp::with_transport(Box::new(transport)); + let _ = app.get_address(3).unwrap(); + + let cmds = commands.borrow(); + let cmd = &cmds[0]; + assert_eq!(cmd.cla, ledger_app::CLA); + assert_eq!(cmd.ins, ledger_app::INS_GET_ADDRESS); + assert_eq!(cmd.p1, 0x00); + assert_eq!(cmd.p2, 0x00); + // data = account_index(4 BE) + address_index(4 BE) = 0x00000000 + 0x00000003 + assert_eq!(cmd.data, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]); + } + + // ── sign_transaction chunking ──────────────────────────────────────────── + + #[test] + fn test_sign_single_chunk() { + let data = vec![0x01u8; 100]; // under 150 bytes → one chunk + let mut app = app_with([MockTransport::ok(sig_response(dummy_sig()))]); + let sig = app.sign_transaction(&data).unwrap(); + assert_eq!(sig, dummy_sig()); + } + + #[test] + fn test_sign_sends_correct_p1_for_first_chunk() { + let data = vec![0x01u8; 100]; + let transport = MockTransport::new([MockTransport::ok(sig_response(dummy_sig()))]); + let commands = transport.commands_spy(); + let mut app = LedgerApp::with_transport(Box::new(transport)); + let _ = app.sign_transaction(&data).unwrap(); + + let cmds = commands.borrow(); + let cmd = &cmds[0]; + assert_eq!(cmd.ins, ledger_app::INS_SIGN_HASH_TX); + assert_eq!(cmd.p1, 0x00); // first chunk + } + + #[test] + fn test_sign_two_chunks_correct_p1() { + // 200 bytes → chunk 1: 150 bytes (p1=0x00), chunk 2: 50 bytes (p1=0x80) + let data = vec![0x42u8; 200]; + let transport = MockTransport::new([ + MockTransport::ok(vec![]), // intermediate chunk + MockTransport::ok(sig_response(dummy_sig())), // final chunk with signature + ]); + let commands = transport.commands_spy(); + let mut app = LedgerApp::with_transport(Box::new(transport)); + let sig = app.sign_transaction(&data).unwrap(); + assert_eq!(sig, dummy_sig()); + + let cmds = commands.borrow(); + assert_eq!(cmds.len(), 2); + assert_eq!(cmds[0].p1, 0x00); // first chunk + assert_eq!(cmds[0].data.len(), 150); + assert_eq!(cmds[1].p1, 0x80); // continuation chunk + assert_eq!(cmds[1].data.len(), 50); + } + + #[test] + fn test_sign_three_chunks() { + // 400 bytes → 3 chunks: 150 + 150 + 100 + let data = vec![0xFFu8; 400]; + let transport = MockTransport::new([ + MockTransport::ok(vec![]), + MockTransport::ok(vec![]), + MockTransport::ok(sig_response(dummy_sig())), + ]); + let commands = transport.commands_spy(); + let mut app = LedgerApp::with_transport(Box::new(transport)); + let sig = app.sign_transaction(&data).unwrap(); + assert_eq!(sig, dummy_sig()); + + let cmds = commands.borrow(); + assert_eq!(cmds[0].p1, 0x00); + assert_eq!(cmds[1].p1, 0x80); + assert_eq!(cmds[2].p1, 0x80); + assert_eq!(cmds[0].data.len(), 150); + assert_eq!(cmds[1].data.len(), 150); + assert_eq!(cmds[2].data.len(), 100); + } + + #[test] + fn test_sign_empty_data_returns_error() { + let mut app = app_with([] as [Result, LedgerError>; 0]); + assert!(matches!( + app.sign_transaction(&[]), + Err(LedgerError::InvalidResponse(_)) + )); + } + + // ── sign_message ───────────────────────────────────────────────────────── + + #[test] + fn test_sign_message_uses_correct_ins() { + let data = b"hello"; + let transport = MockTransport::new([MockTransport::ok(sig_response(dummy_sig()))]); + let commands = transport.commands_spy(); + let mut app = LedgerApp::with_transport(Box::new(transport)); + let _ = app.sign_message(data).unwrap(); + + assert_eq!(commands.borrow()[0].ins, ledger_app::INS_SIGN_MESSAGE); + } + + // ── error mapping ──────────────────────────────────────────────────────── + + #[test] + fn test_user_denied_error() { + let mut app = app_with([MockTransport::err(0x6985)]); + assert!(matches!( + app.get_version(), + Err(LedgerError::StatusWord(0x6985)) + )); + } + + #[test] + fn test_wrong_tx_options_error() { + let mut app = app_with([MockTransport::err(0x6E0F)]); + assert!(matches!( + app.sign_transaction(b"test"), + Err(LedgerError::StatusWord(0x6E0F)) + )); + } + + // ── build_account_address_data ─────────────────────────────────────────── + + #[test] + fn test_address_data_encoding() { + let data = ledger_app::build_account_address_data(7); + // account index 0 (4 bytes BE) + address index 7 (4 bytes BE) + assert_eq!(data, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07]); + } +} diff --git a/sdk/core/src/wallet/ledger/ledger_transport_native.rs b/sdk/core/src/wallet/ledger/ledger_transport_native.rs new file mode 100644 index 0000000000..834e36b197 --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_transport_native.rs @@ -0,0 +1,18 @@ +use ledger_apdu::APDUCommand; +use ledger_transport_hid::TransportNativeHID; + +use super::{LedgerError, LedgerTransport}; + +impl LedgerTransport for TransportNativeHID { + fn exchange(&mut self, command: &APDUCommand>) -> Result, LedgerError> { + let answer = TransportNativeHID::exchange(self, command) + .map_err(|e| LedgerError::Transport(e.to_string()))?; + + let sw = answer.retcode(); + if sw != 0x9000 { + return Err(LedgerError::from_status_word(sw)); + } + + Ok(answer.data().to_vec()) + } +} diff --git a/sdk/core/src/wallet/ledger/ledger_transport_wallet.rs b/sdk/core/src/wallet/ledger/ledger_transport_wallet.rs new file mode 100644 index 0000000000..bae9a9bc1a --- /dev/null +++ b/sdk/core/src/wallet/ledger/ledger_transport_wallet.rs @@ -0,0 +1,205 @@ +use ledger_apdu::APDUCommand; +use multiversx_chain_core::std::crypto; + +use super::{LedgerError, LedgerTransport}; +use crate::wallet::Wallet; + +const INS_GET_APP_CONFIG: u8 = 0x02; +const INS_GET_ADDRESS: u8 = 0x03; +const INS_SET_ADDRESS: u8 = 0x05; +const INS_SIGN_MESSAGE: u8 = 0x06; +const INS_SIGN_HASH_TX: u8 = 0x07; + +/// A [`LedgerTransport`] backed by a software [`Wallet`]. +/// +/// Simulates the MultiversX Ledger app at the APDU level so that code written +/// against [`LedgerApp`](super::LedgerApp) can be tested end-to-end without +/// physical hardware. Signing behaviour matches the real device: +/// +/// - `INS_SIGN_HASH_TX`: signs `keccak256(payload)` with the wallet key. +/// - `INS_SIGN_MESSAGE`: signs the payload bytes directly. +/// +/// Multi-chunk payloads are accumulated across calls (p1 `0x00` = first chunk, +/// `0x80` = continuation) and signed after each exchange, so the caller always +/// receives a valid signature response — only the last one matters. +pub struct WalletTransport { + wallet: Wallet, + sign_buffer: Vec, +} + +impl WalletTransport { + pub fn new(wallet: Wallet) -> Self { + WalletTransport { + wallet, + sign_buffer: Vec::new(), + } + } +} + +impl LedgerTransport for WalletTransport { + fn exchange(&mut self, command: &APDUCommand>) -> Result, LedgerError> { + match command.ins { + INS_GET_APP_CONFIG => { + // data_activated=true, account=0, address=0, version=0.0.0 + Ok(vec![0x01, 0x00, 0x00, 0x00, 0x00, 0x00]) + } + INS_GET_ADDRESS => { + let bech32 = self.wallet.to_bech32().to_string(); + let bytes = bech32.as_bytes(); + let mut response = Vec::with_capacity(1 + bytes.len()); + response.push(bytes.len() as u8); + response.extend_from_slice(bytes); + Ok(response) + } + INS_SET_ADDRESS => Ok(vec![]), + INS_SIGN_HASH_TX | INS_SIGN_MESSAGE => { + if command.p1 == 0x00 { + self.sign_buffer.clear(); + } + self.sign_buffer.extend_from_slice(&command.data); + + let pk = self.wallet.private_key().ok_or_else(|| { + LedgerError::Transport( + "WalletTransport requires a private-key wallet, not a Ledger wallet".into(), + ) + })?; + + let data_to_sign: Vec = if command.ins == INS_SIGN_HASH_TX { + crypto::keccak256(&self.sign_buffer).to_vec() + } else { + self.sign_buffer.clone() + }; + + let sig = pk.sign(&data_to_sign).to_bytes(); + let mut response = Vec::with_capacity(65); + response.push(0x40); + response.extend_from_slice(&sig); + Ok(response) + } + ins => Err(LedgerError::InvalidResponse(format!( + "WalletTransport: unknown instruction 0x{ins:02X}" + ))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wallet::{PrivateKey, WalletSignature}; + use multiversx_chain_core::std::crypto; + + fn test_wallet() -> Wallet { + // Known test key: all-zero seed. + Wallet::from(PrivateKey::from_seed_bytes(&[0u8; 32])) + } + + #[test] + fn test_get_app_config() { + let mut transport = WalletTransport::new(test_wallet()); + let cmd = APDUCommand { + cla: 0xED, + ins: INS_GET_APP_CONFIG, + p1: 0, + p2: 0, + data: vec![], + }; + let response = transport.exchange(&cmd).unwrap(); + assert_eq!(response.len(), 6); + assert_eq!(response[0], 0x01); // data_activated + } + + #[test] + fn test_get_address_returns_bech32() { + let mut transport = WalletTransport::new(test_wallet()); + let cmd = APDUCommand { + cla: 0xED, + ins: INS_GET_ADDRESS, + p1: 0, + p2: 0, + data: vec![], + }; + let response = transport.exchange(&cmd).unwrap(); + let len = response[0] as usize; + let addr = std::str::from_utf8(&response[1..1 + len]).unwrap(); + assert!( + addr.starts_with("erd1"), + "expected bech32 with 'erd1' prefix, got {addr}" + ); + } + + #[test] + fn test_sign_hash_tx_matches_wallet_keccak() { + let wallet = test_wallet(); + let payload = b"hello tx"; + + let mut transport = WalletTransport::new(wallet.clone()); + let cmd = APDUCommand { + cla: 0xED, + ins: INS_SIGN_HASH_TX, + p1: 0x00, + p2: 0, + data: payload.to_vec(), + }; + let response = transport.exchange(&cmd).unwrap(); + assert_eq!(response[0], 0x40); + let ledger_sig = WalletSignature::from_bytes(response[1..].try_into().unwrap()); + + // The wallet signs keccak256(payload) for hash-based transactions. + let hash = crypto::keccak256(payload); + let expected_sig = wallet.private_key().unwrap().sign(hash); + assert_eq!(ledger_sig.to_bytes(), expected_sig.to_bytes()); + } + + #[test] + fn test_sign_message_matches_wallet_direct() { + let wallet = test_wallet(); + let payload = b"hello message"; + + let mut transport = WalletTransport::new(wallet.clone()); + let cmd = APDUCommand { + cla: 0xED, + ins: INS_SIGN_MESSAGE, + p1: 0x00, + p2: 0, + data: payload.to_vec(), + }; + let response = transport.exchange(&cmd).unwrap(); + assert_eq!(response[0], 0x40); + let ledger_sig = WalletSignature::from_bytes(response[1..].try_into().unwrap()); + + let expected_sig = wallet.private_key().unwrap().sign(payload); + assert_eq!(ledger_sig.to_bytes(), expected_sig.to_bytes()); + } + + #[test] + fn test_multi_chunk_accumulates_correctly() { + let wallet = test_wallet(); + let chunk1 = vec![0xAAu8; 150]; + let chunk2 = vec![0xBBu8; 50]; + let full_payload = [chunk1.as_slice(), chunk2.as_slice()].concat(); + + let mut transport = WalletTransport::new(wallet.clone()); + let cmd1 = APDUCommand { + cla: 0xED, + ins: INS_SIGN_HASH_TX, + p1: 0x00, + p2: 0, + data: chunk1, + }; + transport.exchange(&cmd1).unwrap(); // intermediate — discard + let cmd2 = APDUCommand { + cla: 0xED, + ins: INS_SIGN_HASH_TX, + p1: 0x80, + p2: 0, + data: chunk2, + }; + let response = transport.exchange(&cmd2).unwrap(); + + let ledger_sig = WalletSignature::from_bytes(response[1..].try_into().unwrap()); + let hash = crypto::keccak256(&full_payload); + let expected_sig = wallet.private_key().unwrap().sign(hash); + assert_eq!(ledger_sig.to_bytes(), expected_sig.to_bytes()); + } +} diff --git a/sdk/core/src/wallet/mnemonic.rs b/sdk/core/src/wallet/mnemonic.rs new file mode 100644 index 0000000000..580b9d1498 --- /dev/null +++ b/sdk/core/src/wallet/mnemonic.rs @@ -0,0 +1,97 @@ +use std::fmt; + +use anyhow::{Context, Result}; +use hmac::{Hmac, KeyInit, Mac}; +use sha2::Sha512; + +use super::private_key::PrivateKey; + +const EGLD_COIN_TYPE: u32 = 508; +const HARDENED: u32 = 0x80000000; + +/// A BIP-39 mnemonic used to derive [`PrivateKey`]s via the MultiversX HD path. +/// +/// Wraps [`bip39::Mnemonic`] and provides MultiversX-specific key derivation. +#[derive(Clone)] +pub struct Mnemonic(bip39::Mnemonic); + +impl Mnemonic { + /// Parses a mnemonic phrase. + /// + /// All whitespace sequences (spaces, tabs, `\n`, `\r\n`, etc.) are + /// collapsed to single spaces before parsing, so file contents can be + /// passed directly without pre-processing. + pub fn parse(s: &str) -> Result { + let normalized = s.split_whitespace().collect::>().join(" "); + Ok(Mnemonic( + bip39::Mnemonic::parse(&normalized).context("invalid mnemonic phrase")?, + )) + } + + /// Derives a [`PrivateKey`] using the MultiversX HD path + /// `m/44'/508'/'/0'/'`. + pub fn to_private_key(&self, account: u32, address_index: u32) -> Result { + let seed = self.to_bip39_seed(); + self.bip32_derive(&seed, account, address_index) + } + + fn to_bip39_seed(&self) -> [u8; 64] { + self.0.to_seed_normalized("") + } + + fn bip32_derive( + &self, + seed: &[u8; 64], + account: u32, + address_index: u32, + ) -> Result { + let serialized_key_len = 32; + let hardened_child_padding: u8 = 0; + + let mut digest = Hmac::::new_from_slice(b"ed25519 seed") + .context("failed to initialise root HMAC-SHA512 digest")?; + digest.update(seed); + let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); + let mut key = intermediary[..serialized_key_len].to_vec(); + let mut chain_code = intermediary[serialized_key_len..].to_vec(); + + for child_idx in [ + 44 | HARDENED, + EGLD_COIN_TYPE | HARDENED, + account | HARDENED, + HARDENED, + address_index | HARDENED, + ] { + let mut buff = [vec![hardened_child_padding], key.clone()].concat(); + buff.push((child_idx >> 24) as u8); + buff.push((child_idx >> 16) as u8); + buff.push((child_idx >> 8) as u8); + buff.push(child_idx as u8); + + digest = Hmac::::new_from_slice(&chain_code) + .context("failed to initialise child HMAC-SHA512 digest")?; + digest.update(&buff); + let intermediary: Vec = digest.finalize().into_bytes().into_iter().collect(); + key = intermediary[..serialized_key_len].to_vec(); + chain_code = intermediary[serialized_key_len..].to_vec(); + } + + let seed_bytes: &[u8; 32] = key + .as_slice() + .try_into() + .context("BIP32 derived key has unexpected length")?; + Ok(PrivateKey::from_seed_bytes(seed_bytes)) + } +} + +impl From for Mnemonic { + fn from(m: bip39::Mnemonic) -> Self { + Mnemonic(m) + } +} + +impl fmt::Display for Mnemonic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/sdk/core/src/wallet/private_key.rs b/sdk/core/src/wallet/private_key.rs new file mode 100644 index 0000000000..ff1b40e528 --- /dev/null +++ b/sdk/core/src/wallet/private_key.rs @@ -0,0 +1,139 @@ +use std::fmt::Display; + +use anyhow::{Result, anyhow}; +use multiversx_chain_core::std::crypto::ed25519; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Serialize, Serializer}, +}; + +use super::wallet_signature::WalletSignature; + +pub const PRIVATE_KEY_LENGTH: usize = 64; +pub const SEED_LENGTH: usize = 32; + +#[derive(Clone, PartialEq, Eq)] +pub struct PrivateKey(pub(crate) ed25519::Ed25519SigningKey); + +impl PrivateKey { + /// Constructs a [`PrivateKey`] from a 32-byte ed25519 seed. + /// + /// The corresponding public (verifying) key is derived automatically. + /// This is the canonical representation used throughout the MultiversX SDK. + pub fn from_seed_bytes(bytes: &[u8; 32]) -> PrivateKey { + PrivateKey(ed25519::Ed25519SigningKey::from_seed(bytes)) + } + + /// Constructs a [`PrivateKey`] from a 64-byte keypair `[seed || public_key]`. + /// + /// Returns an error if the embedded public key does not match the seed. + pub fn from_keypair_bytes(bytes: &[u8; 64]) -> Result { + ed25519::Ed25519SigningKey::from_keypair_bytes(bytes) + .map(PrivateKey) + .map_err(|e| anyhow!("Invalid keypair bytes: {e}")) + } + + /// Constructs a [`PrivateKey`] from a slice whose length determines the format: + /// - 32 bytes → treated as a seed (see [`from_seed_bytes`](Self::from_seed_bytes)) + /// - 64 bytes → treated as a keypair (see [`from_keypair_bytes`](Self::from_keypair_bytes)) + /// + /// Returns an error for any other length. + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes.len() { + SEED_LENGTH => { + let seed: &[u8; 32] = bytes + .try_into() + .map_err(|_| anyhow!("Invalid secret key length"))?; + Ok(PrivateKey::from_seed_bytes(seed)) + } + PRIVATE_KEY_LENGTH => { + let keypair: &[u8; 64] = bytes + .try_into() + .map_err(|_| anyhow!("Invalid secret key length"))?; + PrivateKey::from_keypair_bytes(keypair) + } + _ => Err(anyhow!("Invalid secret key length")), + } + } + + /// Decodes a hex string into a 32-byte seed and constructs a [`PrivateKey`]. + /// + /// The input must be exactly 64 hex characters (32 bytes). Returns an error + /// if the string is not valid hex or does not decode to exactly 32 bytes. + pub fn from_seed_hex_str(pk: &str) -> Result { + let bytes = hex::decode(pk)?; + let seed: [u8; 32] = bytes + .try_into() + .map_err(|_| anyhow!("Invalid seed key length"))?; + Ok(PrivateKey::from_seed_bytes(&seed)) + } + + /// Decodes a hex string into a 64-byte keypair and constructs a [`PrivateKey`]. + /// + /// The input must be exactly 128 hex characters (64 bytes). Returns an error + /// if the string is not valid hex, does not decode to exactly 64 bytes, or + /// the embedded public key does not match the seed. + pub fn from_keypair_hex_str(pk: &str) -> Result { + let bytes = hex::decode(pk)?; + let keypair: [u8; 64] = bytes + .try_into() + .map_err(|_| anyhow!("Invalid keypair key length"))?; + PrivateKey::from_keypair_bytes(&keypair) + } + + /// Decodes a hex string and constructs a [`PrivateKey`], inferring the format + /// from the decoded length (32 bytes → seed, 64 bytes → keypair). + /// + /// Prefer [`from_seed_hex_str`](Self::from_seed_hex_str) or + /// [`from_keypair_hex_str`](Self::from_keypair_hex_str) when the format is known. + pub fn from_hex_str(pk: &str) -> Result { + let bytes = hex::decode(pk)?; + PrivateKey::from_bytes(bytes.as_slice()) + } + + /// Returns the full 64-byte keypair as `[seed (32 bytes) || public_key (32 bytes)]`. + pub fn to_bytes(&self) -> [u8; PRIVATE_KEY_LENGTH] { + self.0.to_keypair_bytes() + } + + /// Returns the 32-byte seed encoded as a lowercase hex string (64 characters). + pub fn to_seed_hex(&self) -> String { + hex::encode(self.0.to_seed_bytes()) + } + + /// Signs `message` with this key and returns a [`WalletSignature`]. + pub fn sign(&self, message: impl AsRef<[u8]>) -> WalletSignature { + WalletSignature::from(self.0.sign(message.as_ref())) + } +} + +impl Display for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_seed_hex().fmt(f) + } +} + +impl std::fmt::Debug for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PrivateKey({})", self) + } +} + +impl Serialize for PrivateKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_seed_hex().as_str()) + } +} + +impl<'de> Deserialize<'de> for PrivateKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(Self::from_hex_str(s.as_str()).unwrap()) + } +} diff --git a/sdk/core/src/wallet/public_key.rs b/sdk/core/src/wallet/public_key.rs new file mode 100644 index 0000000000..c382ae8aba --- /dev/null +++ b/sdk/core/src/wallet/public_key.rs @@ -0,0 +1,97 @@ +use std::fmt::Display; + +use super::PrivateKey; +use super::wallet_signature::WalletSignature; +use anyhow::Result; +use multiversx_chain_core::std::crypto::ed25519; +use multiversx_chain_core::types::Address; +use serde::{ + de::{Deserialize, Deserializer}, + ser::{Serialize, Serializer}, +}; + +pub const PUBLIC_KEY_LENGTH: usize = 32; + +#[derive(Copy, Clone)] +pub struct PublicKey(ed25519::Ed25519VerifyingKey); + +impl PublicKey { + /// Returns the raw 32-byte public key. + pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { + self.0.to_bytes() + } + + /// Returns a reference to the raw 32-byte public key. + pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_LENGTH] { + self.0.as_bytes() + } + + /// Derives the MultiversX [`Address`] from this public key. + /// + /// The address is the raw 32-byte public key interpreted as an address. + pub fn to_address(&self) -> Address { + (*self.0.as_bytes()).into() + } + + /// Returns the public key encoded as a lowercase hex string (64 characters). + pub fn to_hex(&self) -> String { + hex::encode(self.0.as_bytes()) + } + + /// Decodes a 64-character hex string into a [`PublicKey`]. + /// + /// Returns an error if the string is not valid hex, does not decode to + /// exactly 32 bytes, or the bytes do not represent a valid ed25519 point. + pub fn from_hex_str(pk: &str) -> Result { + let bytes = hex::decode(pk)?; + let bits: [u8; PUBLIC_KEY_LENGTH] = bytes + .try_into() + .map_err(|_| anyhow::anyhow!("invalid public key length, expected 32 bytes"))?; + ed25519::Ed25519VerifyingKey::from_bytes(&bits) + .map(PublicKey) + .ok_or_else(|| anyhow::anyhow!("invalid ed25519 public key")) + } + + /// Verifies that `signature` is a valid ed25519 signature over `message` + /// produced by the private key corresponding to this public key. + pub fn verify(&self, message: &[u8], signature: &WalletSignature) -> bool { + self.0.verify(message, signature.inner()) + } +} + +impl From<&PrivateKey> for PublicKey { + fn from(private_key: &PrivateKey) -> PublicKey { + PublicKey(private_key.0.verifying_key()) + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_hex().fmt(f) + } +} + +impl std::fmt::Debug for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PublicKey({})", self) + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.to_hex().as_str()) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(Self::from_hex_str(s.as_str()).unwrap()) + } +} diff --git a/sdk/core/src/wallet/signer.rs b/sdk/core/src/wallet/signer.rs new file mode 100644 index 0000000000..0fc81c1b49 --- /dev/null +++ b/sdk/core/src/wallet/signer.rs @@ -0,0 +1,20 @@ +use super::PrivateKey; + +/// The mechanism used to sign transactions and messages. +/// +/// Always compiled — no `#[cfg]` on the type or its variants. +/// The `Ledger` variant holds only a `u32` address index, so there is no +/// compile-time dependency on `libhidapi`. +/// +/// Attempting to *sign* with `Signer::Ledger` when the `ledger` feature is +/// not active will return an informative runtime error from [`Wallet::sign_tx`] +/// and [`Wallet::sign_bytes`]. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Signer { + /// Signs locally using an in-memory ed25519 private key. + PrivateKey(Box), + + /// Signs on a connected Ledger hardware device at the given BIP-44 address + /// index (account is always 0 for MultiversX). + Ledger { address_index: u32 }, +} diff --git a/sdk/core/src/wallet/wallet_impl.rs b/sdk/core/src/wallet/wallet_impl.rs new file mode 100644 index 0000000000..293912c8c5 --- /dev/null +++ b/sdk/core/src/wallet/wallet_impl.rs @@ -0,0 +1,241 @@ +use core::str; +use std::path::Path; + +use anyhow::{Result, anyhow}; +use multiversx_chain_core::{ + std::{Bech32Address, Bech32Hrp, crypto}, + types::Address, +}; +use serde_json::json; + +use crate::{ + data::transaction::Transaction, + wallet::{Mnemonic, PrivateKey, PublicKey, Signer, WalletPem, WalletSignature, WalletSource}, +}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Wallet { + pub signer: Signer, + pub address: Address, + pub source: WalletSource, +} + +impl Wallet { + /// Creates a wallet backed by a local private key. + /// The on-chain address is derived automatically from the key. + pub fn new(private_key: PrivateKey, source: WalletSource) -> Self { + let address = PublicKey::from(&private_key).to_address(); + Wallet { + signer: Signer::PrivateKey(Box::new(private_key)), + address, + source, + } + } + + /// Creates a wallet backed by a Ledger hardware device. + /// + /// The `address` must already be fetched from the device (via + /// [`LedgerApp::get_address`]); it is stored as-is and used for + /// transaction-sender validation. + pub fn new_ledger(address: Address, hrp: Bech32Hrp, address_index: u32) -> Self { + Wallet { + signer: Signer::Ledger { address_index }, + address, + source: WalletSource::Ledger { address_index, hrp }, + } + } +} + +impl From for Wallet { + fn from(wallet_pem: WalletPem) -> Self { + Self::new( + wallet_pem.private_key, + WalletSource::PemFile(wallet_pem.address.hrp), + ) + } +} + +impl From for Wallet { + fn from(private_key: PrivateKey) -> Self { + Self::new(private_key, WalletSource::PrivateKey) + } +} + +impl TryFrom for Wallet { + type Error = anyhow::Error; + + /// Derives the wallet at account 0, address index 0 from the mnemonic. + fn try_from(mnemonic: Mnemonic) -> Result { + let private_key = mnemonic.to_private_key(0, 0)?; + Ok(Self::new(private_key, WalletSource::Mnemonic)) + } +} + +impl Wallet { + #[deprecated( + since = "0.67.0", + note = "Use `PrivateKey::from_hex_str(hex).map(Wallet::from)` instead" + )] + pub fn from_private_key_hex(priv_key: &str) -> Result { + let private_key = PrivateKey::from_hex_str(priv_key)?; + Ok(Self::new(private_key, WalletSource::PrivateKey)) + } + + pub fn from_pem_file

(file_path: P) -> Result + where + P: AsRef, + { + Ok(WalletPem::from_pem_file(file_path)?.into()) + } + + pub(crate) fn new_test_wallet(name: &'static str, pem: &str) -> Self { + let wallet_pem = WalletPem::from_pem_str(pem).unwrap(); + Self::new(wallet_pem.private_key, WalletSource::TestWallet(name)) + } + + #[deprecated( + since = "0.54.0", + note = "Renamed to `to_address`, type changed to multiversx_chain_core::types::Address" + )] + pub fn address(&self) -> Bech32Address { + self.to_address().to_bech32_default() + } + + pub fn to_address(&self) -> Address { + self.address.clone() + } + + /// Returns the address as a [`Bech32Address`], using the HRP from the wallet + /// source (`PemFile`, `Keystore`, or `Ledger`) when available, and the + /// default HRP (`"erd"`) otherwise. + pub fn to_bech32(&self) -> Bech32Address { + let hrp = match &self.source { + WalletSource::PemFile(hrp) + | WalletSource::Keystore(hrp) + | WalletSource::Ledger { hrp, .. } => *hrp, + _ => Bech32Hrp::default(), + }; + Bech32Address::encode_address(hrp, self.address.clone()) + } + + /// Returns a reference to the private key, or `None` for Ledger wallets. + pub fn private_key(&self) -> Option<&PrivateKey> { + match &self.signer { + Signer::PrivateKey(pk) => Some(pk), + Signer::Ledger { .. } => None, + } + } + + /// Returns the private key as a hex-encoded seed string, or `None` for Ledger wallets. + pub fn private_key_hex(&self) -> Option { + self.private_key().map(|pk| pk.to_seed_hex()) + } + + /// Returns the public key derived from the private key, or `None` for Ledger wallets. + pub fn public_key(&self) -> Option { + self.private_key().map(PublicKey::from) + } + + /// Returns the public key as a hex string, or `None` for Ledger wallets. + pub fn public_key_hex(&self) -> Option { + self.public_key().map(|pk| pk.to_hex()) + } + + /// Signs a transaction. + /// + /// For `PrivateKey` wallets the signing is local and infallible (barring + /// bugs). For `Ledger` wallets the device must be connected and the user + /// must confirm on-screen; errors are returned as `Err`. + pub fn sign_tx(&self, unsign_tx: &Transaction) -> Result { + let mut unsign_tx = unsign_tx.clone(); + unsign_tx.signature = None; + unsign_tx.relayer_signature = None; + + match &self.signer { + Signer::PrivateKey(pk) => { + let mut tx_bytes = json!(unsign_tx).to_string().into_bytes(); + if unsign_tx.should_sign_with_hash() { + tx_bytes = crypto::keccak256(&tx_bytes).to_vec(); + } + Ok(pk.sign(tx_bytes)) + } + Signer::Ledger { address_index } => ledger_sign_tx(*address_index, &unsign_tx), + } + } + + /// Signs arbitrary bytes. + /// + /// For `Ledger` wallets the message bytes are prefixed with a 4-byte + /// big-endian length before being sent to the device (matching the Python SDK). + pub fn sign_bytes(&self, data: impl AsRef<[u8]>) -> Result { + match &self.signer { + Signer::PrivateKey(pk) => Ok(pk.sign(data)), + Signer::Ledger { address_index } => ledger_sign_bytes(*address_index, data), + } + } + + /// Converts this wallet to a PEM representation. + /// Returns an error for Ledger wallets (the private key is never exported). + pub fn to_pem(&self, hrp: Bech32Hrp) -> Result { + let pk = self.private_key().ok_or_else(|| { + anyhow!("cannot export a Ledger wallet to PEM: the private key is not available") + })?; + Ok(WalletPem { + private_key: pk.clone(), + address: Bech32Address::encode_address(hrp, self.address.clone()), + }) + } +} + +#[cfg(feature = "ledger")] +fn ledger_sign_tx(address_index: u32, tx: &Transaction) -> Result { + use crate::wallet::ledger::LedgerApp; + use anyhow::Context as _; + + if !tx.should_sign_with_hash() { + return Err(anyhow!( + "Ledger signing requires hash-based options: \ + set version to V2 and options to SIGN_WITH_HASH" + )); + } + + let tx_bytes = json!(tx).to_string().into_bytes(); + let mut app = LedgerApp::new() + .context("failed to open Ledger device — check that it is plugged in, unlocked, and the MultiversX app is open")?; + app.set_address(address_index) + .with_context(|| format!("failed to set Ledger address index {address_index}"))?; + let sig_bytes = app + .sign_transaction(&tx_bytes) + .context("Ledger rejected the transaction — confirm it on the device screen or check that the transaction is valid")?; + Ok(WalletSignature::from_bytes(sig_bytes)) +} + +#[cfg(not(feature = "ledger"))] +fn ledger_sign_tx(_address_index: u32, _tx: &Transaction) -> Result { + Err(anyhow!( + "Ledger signing is not available; recompile with the `ledger` feature enabled" + )) +} + +#[cfg(feature = "ledger")] +fn ledger_sign_bytes(address_index: u32, data: impl AsRef<[u8]>) -> Result { + use crate::wallet::ledger::LedgerApp; + + let data_ref = data.as_ref(); + let len_prefix = (data_ref.len() as u32).to_be_bytes(); + let mut msg_bytes = Vec::with_capacity(4 + data_ref.len()); + msg_bytes.extend_from_slice(&len_prefix); + msg_bytes.extend_from_slice(data_ref); + + let mut app = LedgerApp::new().map_err(|e| anyhow!("{e}"))?; + app.set_address(address_index).map_err(|e| anyhow!("{e}"))?; + let sig_bytes = app.sign_message(&msg_bytes).map_err(|e| anyhow!("{e}"))?; + Ok(WalletSignature::from_bytes(sig_bytes)) +} + +#[cfg(not(feature = "ledger"))] +fn ledger_sign_bytes(_address_index: u32, _data: impl AsRef<[u8]>) -> Result { + Err(anyhow!( + "Ledger signing is not available; recompile with the `ledger` feature enabled" + )) +} diff --git a/sdk/core/src/wallet/wallet_pem.rs b/sdk/core/src/wallet/wallet_pem.rs new file mode 100644 index 0000000000..9d1ad1bddf --- /dev/null +++ b/sdk/core/src/wallet/wallet_pem.rs @@ -0,0 +1,99 @@ +use std::path::Path; + +use anyhow::{Result, anyhow}; +use multiversx_chain_core::std::Bech32Address; + +use multiversx_chain_core::std::{base64_decode, base64_encode}; + +use super::{PrivateKey, PublicKey}; + +const PEM_BEGIN_PREFIX: &str = "-----BEGIN PRIVATE KEY for "; +const PEM_END_PREFIX: &str = "-----END PRIVATE KEY for "; +const PEM_MARKER_SUFFIX: &str = "-----"; + +/// A wallet loaded from or destined for the PEM format. +/// +/// The address field carries both the 32-byte address and the HRP, so a +/// round-trip through [`WalletPem::from_pem_str`] / [`WalletPem::to_pem_str`] +/// preserves the original human-readable part (e.g. `"erd"` or a custom one). +pub struct WalletPem { + pub private_key: PrivateKey, + pub address: Bech32Address, +} + +impl WalletPem { + /// Parses a PEM string into a `WalletPem`. + /// + /// The first line must be `-----BEGIN PRIVATE KEY for {bech32}-----`, which + /// supplies the address together with its HRP. The base64-encoded body + /// contains the concatenated private-key hex and public-key hex. + pub fn from_pem_str(pem_str: &str) -> Result { + let mut lines = pem_str.lines(); + + let header = lines.next().ok_or_else(|| anyhow!("PEM string is empty"))?; + let bech32_str = header + .strip_prefix(PEM_BEGIN_PREFIX) + .and_then(|s| s.strip_suffix(PEM_MARKER_SUFFIX)) + .ok_or_else(|| anyhow!("invalid PEM header: {header}"))?; + let address = Bech32Address::from_bech32_str(bech32_str); + + let b64_body: String = lines + .take_while(|line| !line.starts_with(PEM_END_PREFIX)) + .collect(); + + let decoded = + base64_decode(b64_body).map_err(|e| anyhow!("invalid base64 in PEM body: {e}"))?; + let private_key_bytes = &decoded[..decoded.len() / 2]; + let private_key_str = std::str::from_utf8(private_key_bytes) + .map_err(|e| anyhow!("invalid UTF-8 in private key: {e}"))?; + let private_key = PrivateKey::from_seed_hex_str(private_key_str)?; + + Ok(WalletPem { + private_key, + address, + }) + } + + /// Reads a PEM file from disk and parses it with [`WalletPem::from_pem_str`]. + pub fn from_pem_file

(file_path: P) -> Result + where + P: AsRef, + { + let contents = std::fs::read_to_string(file_path)?; + Self::from_pem_str(&contents) + } + + /// Produces a PEM string from this `WalletPem`. + /// + /// Output format: + /// ```text + /// -----BEGIN PRIVATE KEY for {bech32}----- + /// {base64(private_key_hex + public_key_hex)} + /// -----END PRIVATE KEY for {bech32}----- + /// ``` + pub fn to_pem_str(&self) -> String { + let private_key = self.private_key_hex(); + let public_key = self.public_key_hex(); + let b64 = base64_encode(format!("{private_key}{public_key}")); + + let formatted_key = b64 + .as_bytes() + .chunks(64) + .map(|chunk| std::str::from_utf8(chunk).unwrap()) + .collect::>() + .join("\n"); + + let bech32 = self.address.to_bech32_str(); + format!( + "{PEM_BEGIN_PREFIX}{bech32}{PEM_MARKER_SUFFIX}\n{formatted_key}\n{PEM_END_PREFIX}{bech32}{PEM_MARKER_SUFFIX}\n" + ) + } + + pub fn private_key_hex(&self) -> String { + self.private_key.to_seed_hex() + } + + pub fn public_key_hex(&self) -> String { + PublicKey::from(&self.private_key).to_hex() + } +} diff --git a/sdk/core/src/wallet/wallet_signature.rs b/sdk/core/src/wallet/wallet_signature.rs new file mode 100644 index 0000000000..352dc29144 --- /dev/null +++ b/sdk/core/src/wallet/wallet_signature.rs @@ -0,0 +1,78 @@ +use std::fmt; + +use anyhow::Result; +use multiversx_chain_core::std::crypto::ed25519; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// An Ed25519 signature, serialized as a lowercase hex string. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct WalletSignature(ed25519::Ed25519Signature); + +impl WalletSignature { + pub fn from_bytes(bytes: [u8; 64]) -> Self { + Self(ed25519::Ed25519Signature::from_bytes(&bytes)) + } + + pub fn to_bytes(&self) -> [u8; 64] { + self.0.to_bytes() + } + + pub fn inner(&self) -> &ed25519::Ed25519Signature { + &self.0 + } + + pub fn to_hex(&self) -> String { + hex::encode(self.to_bytes()) + } + + pub fn from_hex_str(s: &str) -> Result { + let bytes = hex::decode(s)?; + let bits: [u8; 64] = bytes + .try_into() + .map_err(|_| anyhow::anyhow!("invalid signature length, expected 64 bytes"))?; + Ok(Self::from_bytes(bits)) + } +} + +impl From<[u8; 64]> for WalletSignature { + fn from(bytes: [u8; 64]) -> Self { + Self::from_bytes(bytes) + } +} + +impl From for WalletSignature { + fn from(sig: ed25519::Ed25519Signature) -> Self { + Self(sig) + } +} + +impl fmt::Display for WalletSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.to_hex().fmt(f) + } +} + +impl fmt::Debug for WalletSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WalletSignature({})", self) + } +} + +impl Serialize for WalletSignature { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_hex()) + } +} + +impl<'de> Deserialize<'de> for WalletSignature { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_hex_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/sdk/core/src/wallet/wallet_source.rs b/sdk/core/src/wallet/wallet_source.rs new file mode 100644 index 0000000000..ad4945b26f --- /dev/null +++ b/sdk/core/src/wallet/wallet_source.rs @@ -0,0 +1,17 @@ +use multiversx_chain_core::std::Bech32Hrp; + +/// Optional structure that indicates how the [`Wallet`] was created, with additional metadata. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum WalletSource { + Mnemonic, + PrivateKey, + PemFile(Bech32Hrp), + TestWallet(&'static str), + Keystore(Bech32Hrp), + /// Wallet loaded from a Ledger hardware device. Carries the HRP (for bech32 + /// encoding) and the BIP-44 address index used on the device. + Ledger { + address_index: u32, + hrp: Bech32Hrp, + }, +} diff --git a/sdk/core/tests/private_key_test.rs b/sdk/core/tests/private_key_test.rs new file mode 100644 index 0000000000..5d594a763e --- /dev/null +++ b/sdk/core/tests/private_key_test.rs @@ -0,0 +1,101 @@ +use multiversx_sdk::wallet::{PRIVATE_KEY_LENGTH, PrivateKey, SEED_LENGTH}; + +// Alice's well-known key material (consistent with wallet_pem_test.rs). +const ALICE_SEED_HEX: &str = "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; +const ALICE_PUBLIC_KEY_HEX: &str = + "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; + +fn alice_seed_bytes() -> Vec { + hex::decode(ALICE_SEED_HEX).unwrap() +} + +fn alice_64byte_hex() -> String { + format!("{}{}", ALICE_SEED_HEX, ALICE_PUBLIC_KEY_HEX) +} + +// --------------------------------------------------------------------------- +// from_bytes — 32-byte (seed) branch +// --------------------------------------------------------------------------- + +#[test] +fn test_from_bytes_seed_length_ok() { + let seed = alice_seed_bytes(); + assert_eq!(seed.len(), SEED_LENGTH); + + let pk = PrivateKey::from_seed_bytes(seed.as_slice().try_into().unwrap()); + + // to_seed_hex() encodes only the first 32 bytes (the seed). + assert_eq!(pk.to_seed_hex(), ALICE_SEED_HEX); +} + +#[test] +fn test_from_bytes_seed_derives_correct_public_key() { + let seed = alice_seed_bytes(); + let pk = PrivateKey::from_seed_bytes(seed.as_slice().try_into().unwrap()); + + // The upper 32 bytes of the stored key must be the verifying key. + let stored = pk.to_bytes(); + let derived_pubkey_hex = hex::encode(&stored[32..]); + assert_eq!(derived_pubkey_hex, ALICE_PUBLIC_KEY_HEX); +} + +#[test] +fn test_from_bytes_seed_roundtrip() { + let seed = alice_seed_bytes(); + let pk = PrivateKey::from_seed_bytes(seed.as_slice().try_into().unwrap()); + + // Re-constructing from the returned 64-byte array must yield an equal key. + let pk2 = PrivateKey::from_keypair_bytes(&pk.to_bytes()).unwrap(); + assert_eq!(pk, pk2); +} + +// --------------------------------------------------------------------------- +// from_bytes — 64-byte (full key) branch +// --------------------------------------------------------------------------- + +#[test] +fn test_from_bytes_full_length_ok() { + let full = hex::decode(alice_64byte_hex()).unwrap(); + assert_eq!(full.len(), PRIVATE_KEY_LENGTH); + + let pk = PrivateKey::from_keypair_bytes(full.as_slice().try_into().unwrap()).unwrap(); + + // Seed portion must be preserved verbatim. + assert_eq!(pk.to_seed_hex(), ALICE_SEED_HEX); +} + +#[test] +fn test_from_bytes_full_preserves_all_64_bytes() { + let full = hex::decode(alice_64byte_hex()).unwrap(); + let pk = PrivateKey::from_keypair_bytes(full.as_slice().try_into().unwrap()).unwrap(); + + assert_eq!(pk.to_bytes().as_slice(), full.as_slice()); +} + +#[test] +fn test_from_bytes_full_roundtrip() { + let full = hex::decode(alice_64byte_hex()).unwrap(); + let pk = PrivateKey::from_keypair_bytes(full.as_slice().try_into().unwrap()).unwrap(); + let pk2 = PrivateKey::from_keypair_bytes(&pk.to_bytes()).unwrap(); + assert_eq!(pk, pk2); +} + +// --------------------------------------------------------------------------- +// from_bytes — error cases +// --------------------------------------------------------------------------- + +#[test] +fn test_from_bytes_empty_is_err() { + assert!(PrivateKey::from_bytes(&[]).is_err()); +} + +#[test] +fn test_from_bytes_wrong_length_is_err() { + // 16 bytes — neither 32 nor 64. + assert!(PrivateKey::from_bytes(&[0u8; 16]).is_err()); +} + +#[test] +fn test_from_bytes_65_bytes_is_err() { + assert!(PrivateKey::from_bytes(&[0u8; 65]).is_err()); +} diff --git a/sdk/core/tests/public_key_test.rs b/sdk/core/tests/public_key_test.rs new file mode 100644 index 0000000000..0600c4d302 --- /dev/null +++ b/sdk/core/tests/public_key_test.rs @@ -0,0 +1,70 @@ +use multiversx_sdk::wallet::PublicKey; + +// Alice's known values, consistent with wallet_pem_test.rs constants. +const ALICE_PUBLIC_KEY_HEX: &str = + "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; +const ALICE_BECH32: &str = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + +// --------------------------------------------------------------------------- +// from_hex_str +// --------------------------------------------------------------------------- + +#[test] +fn test_from_hex_str_valid() { + let pk = PublicKey::from_hex_str(ALICE_PUBLIC_KEY_HEX).unwrap(); + assert_eq!(pk.to_hex(), ALICE_PUBLIC_KEY_HEX); +} + +#[test] +fn test_from_hex_str_invalid_hex() { + assert!(PublicKey::from_hex_str("not_valid_hex!!").is_err()); +} + +#[test] +fn test_from_hex_str_too_short() { + // 8 bytes — not 32 + assert!(PublicKey::from_hex_str("0102030405060708").is_err()); +} + +#[test] +fn test_from_hex_str_too_long() { + // 64 bytes (128 hex chars) — not 32 + let long_hex = "01".repeat(64); + assert!(PublicKey::from_hex_str(&long_hex).is_err()); +} + +// --------------------------------------------------------------------------- +// to_hex +// --------------------------------------------------------------------------- + +#[test] +fn test_to_hex_matches_input() { + let pk = PublicKey::from_hex_str(ALICE_PUBLIC_KEY_HEX).unwrap(); + assert_eq!(pk.to_hex(), ALICE_PUBLIC_KEY_HEX); +} + +#[test] +fn test_to_hex_from_hex_roundtrip() { + let pk = PublicKey::from_hex_str(ALICE_PUBLIC_KEY_HEX).unwrap(); + let pk2 = PublicKey::from_hex_str(&pk.to_hex()).unwrap(); + assert_eq!(pk.to_bytes(), pk2.to_bytes()); +} + +// --------------------------------------------------------------------------- +// to_address +// --------------------------------------------------------------------------- + +#[test] +fn test_to_address_bytes_match_public_key() { + // On MultiversX the address IS the public key bytes. + let pk = PublicKey::from_hex_str(ALICE_PUBLIC_KEY_HEX).unwrap(); + let address = pk.to_address(); + assert_eq!(address.to_hex(), ALICE_PUBLIC_KEY_HEX); +} + +#[test] +fn test_to_address_bech32() { + let pk = PublicKey::from_hex_str(ALICE_PUBLIC_KEY_HEX).unwrap(); + let address = pk.to_address(); + assert_eq!(address.to_bech32_default().to_bech32_str(), ALICE_BECH32); +} diff --git a/sdk/core/tests/retrieve_tx_on_network_test.rs b/sdk/core/tests/retrieve_tx_on_network_test.rs index bac74f6d5d..ee9c45e975 100644 --- a/sdk/core/tests/retrieve_tx_on_network_test.rs +++ b/sdk/core/tests/retrieve_tx_on_network_test.rs @@ -1,11 +1,10 @@ -use multiversx_chain_core::types::ReturnCode; +use multiversx_chain_core::{std::base64_decode, types::ReturnCode}; use multiversx_sdk::{ data::transaction::{ApiTransactionResult, GetTransactionResponse}, retrieve_tx_on_network::{ extract_message_from_string_reason, find_code_and_message, parse_reason, replace_with_error_message, }, - utils::base64_decode, }; #[test] @@ -869,7 +868,7 @@ fn replace_logs_reason_sc_panic_test() { serde_json::from_str::(tx_str).unwrap(); let expected_tx: ApiTransactionResult = serde_json::from_str::(expected_tx_str).unwrap(); - replace_with_error_message(&mut tx, &String::from_utf8(base64_decode("c3RvcmFnZSBkZWNvZGUgZXJyb3IgKGtleTogcG9vbENvbnRyYWN0AAAAAAAAAe+/vSk6IGlucHV0IHRvbyBzaG9ydA==")).unwrap()); + replace_with_error_message(&mut tx, &String::from_utf8(base64_decode("c3RvcmFnZSBkZWNvZGUgZXJyb3IgKGtleTogcG9vbENvbnRyYWN0AAAAAAAAAe+/vSk6IGlucHV0IHRvbyBzaG9ydA==").unwrap()).unwrap()); assert_eq!( expected_tx.logs.unwrap().events[0].topics, tx.logs.unwrap().events[0].topics diff --git a/sdk/core/tests/wallet_keystore_test.rs b/sdk/core/tests/wallet_keystore_test.rs new file mode 100644 index 0000000000..04be4ded20 --- /dev/null +++ b/sdk/core/tests/wallet_keystore_test.rs @@ -0,0 +1,160 @@ +use multiversx_chain_core::std::Bech32Hrp; +use multiversx_sdk::{ + wallet::{Keystore, KeystoreError, KeystoreRandomness, WalletSource}, + wallet::{PrivateKey, PublicKey}, +}; + +// Well-known Alice private key used across the test suite. +const ALICE_PRIVATE_KEY_HEX: &str = + "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; +const KEYSTORE_PASSWORD: &str = "test_password"; + +/// Fully deterministic randomness: all-zeroes salt, all-ones IV, fixed UUID. +fn fixed_randomness() -> KeystoreRandomness { + KeystoreRandomness { + salt: [0u8; 32], + iv: [1u8; 16], + id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890".to_string(), + } +} + +fn alice_private_key() -> PrivateKey { + PrivateKey::from_seed_hex_str(ALICE_PRIVATE_KEY_HEX).unwrap() +} + +fn erd_hrp() -> Bech32Hrp { + Bech32Hrp::try_from("erd").unwrap() +} + +// --------------------------------------------------------------------------- +// Encrypt / decrypt +// --------------------------------------------------------------------------- + +#[test] +fn test_keystore_encrypt_decrypt_roundtrip() { + let private_key = alice_private_key(); + let keystore = Keystore::encrypt( + &private_key, + erd_hrp(), + KEYSTORE_PASSWORD, + fixed_randomness(), + ); + + let wallet = keystore.decrypt_wallet(KEYSTORE_PASSWORD).unwrap(); + + assert_eq!(wallet.private_key().unwrap(), &private_key); + assert_eq!( + wallet.source, + WalletSource::Keystore(Bech32Hrp::try_from("erd").unwrap()) + ); + assert_eq!(wallet.address, PublicKey::from(&private_key).to_address(),); +} + +#[test] +fn test_keystore_wrong_password_rejected() { + let keystore = Keystore::encrypt( + &alice_private_key(), + erd_hrp(), + KEYSTORE_PASSWORD, + fixed_randomness(), + ); + + assert!(matches!( + keystore.decrypt_wallet("wrong_password"), + Err(KeystoreError::InvalidPassword) + )); +} + +// --------------------------------------------------------------------------- +// Serialization / deserialization +// --------------------------------------------------------------------------- + +#[test] +fn test_keystore_serialize_deserialize_roundtrip() { + let private_key = alice_private_key(); + let keystore = Keystore::encrypt( + &private_key, + erd_hrp(), + KEYSTORE_PASSWORD, + fixed_randomness(), + ); + + let json = keystore.to_json_string(); + + let keystore2: Keystore = Keystore::from_json(serde_json::from_str(&json).unwrap()).unwrap(); + let wallet = keystore2.decrypt_wallet(KEYSTORE_PASSWORD).unwrap(); + + assert_eq!(wallet.private_key().unwrap(), &private_key); +} + +#[test] +fn test_keystore_json_snapshot() { + let keystore = Keystore::encrypt( + &alice_private_key(), + erd_hrp(), + KEYSTORE_PASSWORD, + fixed_randomness(), + ); + + let json = keystore.to_json_string(); + + assert_eq!(json, EXPECTED_KEYSTORE_JSON); +} + +#[test] +fn test_full_encrypt_serialize_deserialize_decrypt_sign() { + const MESSAGE: &[u8] = b"hello multiversx"; + + // Encrypt. + let keystore = Keystore::encrypt( + &alice_private_key(), + erd_hrp(), + KEYSTORE_PASSWORD, + fixed_randomness(), + ); + + // Serialize to JSON, then deserialize back. + let json = keystore.to_json_string(); + let keystore2 = Keystore::from_json(serde_json::from_str(&json).unwrap()).unwrap(); + + // Decrypt into a Wallet. + let wallet = keystore2.decrypt_wallet(KEYSTORE_PASSWORD).unwrap(); + assert_eq!(wallet.private_key().unwrap(), &alice_private_key()); + + // Sign the message. + let sig = wallet.sign_bytes(MESSAGE).unwrap(); + + // Ed25519 is deterministic: same key + same message always gives the same signature. + assert_eq!(sig, wallet.sign_bytes(MESSAGE).unwrap()); + + // Snapshot: exact signature bytes must match. + assert_eq!(sig.to_hex(), EXPECTED_SIGNATURE_HEX); +} + +// Generated by running the test suite once with fixed randomness. +const EXPECTED_KEYSTORE_JSON: &str = r#"{ + "version": 4, + "kind": "secretKey", + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "crypto": { + "ciphertext": "66f761de7f386c3cde5d8b1650c37614263880e96314b3070c45a39e44eb7b46573174ddade22c2a5f7dae73385d5a0abc5cff566e34152867c0ff560f1d5d05", + "cipherparams": { + "iv": "01010101010101010101010101010101" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "0000000000000000000000000000000000000000000000000000000000000000", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "4f9db1601de9013e32b2a8ca6cd941b5f5a3feb7a592c0b2586f6b8c1b2a87b9" + } +} +"#; + +const EXPECTED_SIGNATURE_HEX: &str = "c32f811c809a02d3548f190b134d5fa542b028935f4e300556e29b3164f146d54aa7e37ba97080e9f72402af67f6f6f070cbbd1b496c9779e75038a6aee54c07"; diff --git a/sdk/core/tests/wallet_pem_test.rs b/sdk/core/tests/wallet_pem_test.rs new file mode 100644 index 0000000000..61a4ec7e9a --- /dev/null +++ b/sdk/core/tests/wallet_pem_test.rs @@ -0,0 +1,97 @@ +use multiversx_chain_core::std::Bech32Hrp; +use multiversx_sdk::wallet::{Wallet, WalletPem, WalletSource}; + +const ALICE_PEM_PATH: &str = "tests/alice.pem"; +const ALICE_BECH32: &str = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; +const ALICE_PRIVATE_KEY_HEX: &str = + "413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f25e344e8f9"; +const ALICE_PUBLIC_KEY_HEX: &str = + "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1"; + +// --------------------------------------------------------------------------- +// WalletPem parsing +// --------------------------------------------------------------------------- + +#[test] +fn test_pem_from_file_address() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + assert_eq!(pem.address.to_bech32_str(), ALICE_BECH32); +} + +#[test] +fn test_pem_from_file_private_key() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + assert_eq!(pem.private_key_hex(), ALICE_PRIVATE_KEY_HEX); +} + +#[test] +fn test_pem_from_file_public_key() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + assert_eq!(pem.public_key_hex(), ALICE_PUBLIC_KEY_HEX); +} + +#[test] +fn test_pem_from_str_parses_correctly() { + let pem_str = std::fs::read_to_string(ALICE_PEM_PATH).unwrap(); + let pem = WalletPem::from_pem_str(&pem_str).unwrap(); + assert_eq!(pem.address.to_bech32_str(), ALICE_BECH32); + assert_eq!(pem.private_key_hex(), ALICE_PRIVATE_KEY_HEX); + assert_eq!(pem.public_key_hex(), ALICE_PUBLIC_KEY_HEX); +} + +// --------------------------------------------------------------------------- +// WalletPem serialization +// --------------------------------------------------------------------------- + +#[test] +fn test_pem_str_roundtrip() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + let serialized = pem.to_pem_str(); + let reparsed = WalletPem::from_pem_str(&serialized).unwrap(); + assert_eq!(reparsed.private_key_hex(), ALICE_PRIVATE_KEY_HEX); + assert_eq!(reparsed.address.to_bech32_str(), ALICE_BECH32); +} + +// --------------------------------------------------------------------------- +// WalletPem <-> Wallet conversion +// --------------------------------------------------------------------------- + +#[test] +fn test_wallet_from_pem_fields() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + let wallet = Wallet::from(pem); + assert_eq!( + wallet.private_key().unwrap().to_seed_hex(), + ALICE_PRIVATE_KEY_HEX + ); + assert_eq!(wallet.address.to_bech32_default().bech32, ALICE_BECH32); +} + +#[test] +fn test_wallet_from_pem_source() { + let pem = WalletPem::from_pem_file(ALICE_PEM_PATH).unwrap(); + let hrp = pem.address.hrp; + let wallet = Wallet::from(pem); + assert_eq!(wallet.source, WalletSource::PemFile(hrp)); +} + +#[test] +fn test_wallet_to_pem_roundtrip() { + let wallet = Wallet::from_pem_file(ALICE_PEM_PATH).unwrap(); + let hrp = Bech32Hrp::try_from("erd").unwrap(); + let pem = wallet.to_pem(hrp).unwrap(); + assert_eq!(pem.private_key_hex(), ALICE_PRIVATE_KEY_HEX); + assert_eq!(pem.address.to_bech32_str(), ALICE_BECH32); +} + +#[test] +fn test_wallet_to_pem_str_roundtrip() { + let wallet = Wallet::from_pem_file(ALICE_PEM_PATH).unwrap(); + let hrp = Bech32Hrp::try_from("erd").unwrap(); + let pem_str = wallet.to_pem(hrp).unwrap().to_pem_str(); + let reparsed = Wallet::from(WalletPem::from_pem_str(&pem_str).unwrap()); + assert_eq!( + reparsed.private_key().unwrap().to_seed_hex(), + ALICE_PRIVATE_KEY_HEX + ); +} diff --git a/sdk/core/tests/wallet_sign_test.rs b/sdk/core/tests/wallet_sign_test.rs new file mode 100644 index 0000000000..0043fe0a54 --- /dev/null +++ b/sdk/core/tests/wallet_sign_test.rs @@ -0,0 +1,140 @@ +use multiversx_sdk::{test_wallets, wallet::WalletSignature}; + +#[test] +fn test_sign_bytes_snapshot() { + let alice = test_wallets::alice(); + let bob = test_wallets::bob(); + + // Signatures are deterministic: same key + same message always produces the same bytes. + let sig_alice = alice.sign_bytes(b"hello multiversx").unwrap(); + let sig_bob = bob.sign_bytes(b"hello multiversx").unwrap(); + + assert_eq!(sig_alice.to_hex(), SIG_ALICE_HELLO); + assert_eq!(sig_bob.to_hex(), SIG_BOB_HELLO); +} + +#[test] +fn test_sign_bytes_deterministic() { + let alice = test_wallets::alice(); + assert_eq!( + alice.sign_bytes(b"deterministic").unwrap(), + alice.sign_bytes(b"deterministic").unwrap(), + ); +} + +#[test] +fn test_sign_bytes_different_messages_differ() { + let alice = test_wallets::alice(); + assert_ne!( + alice.sign_bytes(b"message one").unwrap(), + alice.sign_bytes(b"message two").unwrap(), + ); +} + +#[test] +fn test_sign_bytes_different_keys_differ() { + assert_ne!( + test_wallets::alice().sign_bytes(b"same message").unwrap(), + test_wallets::bob().sign_bytes(b"same message").unwrap(), + ); +} + +#[test] +fn test_verify_valid_signature() { + let alice = test_wallets::alice(); + let message = b"hello multiversx"; + let sig = alice.sign_bytes(message).unwrap(); + assert!(alice.public_key().unwrap().verify(message, &sig)); +} + +#[test] +fn test_verify_wrong_message_fails() { + let alice = test_wallets::alice(); + let sig = alice.sign_bytes(b"hello multiversx").unwrap(); + assert!( + !alice + .public_key() + .unwrap() + .verify(b"different message", &sig) + ); +} + +#[test] +fn test_verify_wrong_key_fails() { + let alice = test_wallets::alice(); + let bob = test_wallets::bob(); + let sig = alice.sign_bytes(b"hello multiversx").unwrap(); + assert!(!bob.public_key().unwrap().verify(b"hello multiversx", &sig)); +} + +#[test] +fn test_verify_snapshot_signatures() { + let alice = test_wallets::alice(); + let bob = test_wallets::bob(); + let alice_sig = WalletSignature::from_hex_str(SIG_ALICE_HELLO).unwrap(); + let bob_sig = WalletSignature::from_hex_str(SIG_BOB_HELLO).unwrap(); + assert!( + alice + .public_key() + .unwrap() + .verify(b"hello multiversx", &alice_sig) + ); + assert!( + bob.public_key() + .unwrap() + .verify(b"hello multiversx", &bob_sig) + ); +} + +// Snapshots captured with fixed test-wallet keys. +const SIG_ALICE_HELLO: &str = "c32f811c809a02d3548f190b134d5fa542b028935f4e300556e29b3164f146d54aa7e37ba97080e9f72402af67f6f6f070cbbd1b496c9779e75038a6aee54c07"; +const SIG_BOB_HELLO: &str = "09e1b11a87e47bb0c1bebc71f653daabb966c583219588d4b80e396a966b91d41767af8f4704bbc50f8e8fd34cc6fb0da210329ea19437a7e07c0fc3f5cdf50e"; + +#[test] +fn test_wallet_signature_to_hex_roundtrip() { + let raw = [0xabu8; 64]; + let sig = WalletSignature::from_bytes(raw); + let hex = sig.to_hex(); + assert_eq!(hex.len(), 128); + let sig2 = WalletSignature::from_hex_str(&hex).unwrap(); + assert_eq!(sig2, sig); +} + +#[test] +fn test_wallet_signature_from_hex_str_invalid_hex() { + assert!(WalletSignature::from_hex_str("not-hex").is_err()); +} + +#[test] +fn test_wallet_signature_from_hex_str_wrong_length() { + // 63 bytes (126 hex chars) — too short + assert!(WalletSignature::from_hex_str(&"ab".repeat(63)).is_err()); + // 65 bytes (130 hex chars) — too long + assert!(WalletSignature::from_hex_str(&"ab".repeat(65)).is_err()); +} + +#[test] +fn test_wallet_signature_json_serialize_as_hex() { + let raw = [0x01u8; 64]; + let sig = WalletSignature::from_bytes(raw); + let json = serde_json::to_string(&sig).unwrap(); + // JSON value should be a quoted hex string of length 128 chars + assert_eq!(json, format!("\"{}\"", sig.to_hex())); +} + +#[test] +fn test_wallet_signature_json_deserialize_from_hex() { + let hex = "ab".repeat(64); + let json = format!("\"{}\"", hex); + let sig: WalletSignature = serde_json::from_str(&json).unwrap(); + assert_eq!(sig.to_hex(), hex); +} + +#[test] +fn test_wallet_signature_json_roundtrip() { + let raw = [0xdeu8; 64]; + let sig = WalletSignature::from_bytes(raw); + let json = serde_json::to_string(&sig).unwrap(); + let sig2: WalletSignature = serde_json::from_str(&json).unwrap(); + assert_eq!(sig, sig2); +} diff --git a/sdk/core/tests/wallet_test.rs b/sdk/core/tests/wallet_test.rs index 693cc2dce6..523d59730c 100644 --- a/sdk/core/tests/wallet_test.rs +++ b/sdk/core/tests/wallet_test.rs @@ -1,38 +1,37 @@ -use bip39::Mnemonic; - use multiversx_sdk::test_wallets; -use multiversx_sdk::{crypto::public_key::PublicKey, wallet::Wallet}; +use multiversx_sdk::wallet::Mnemonic; +use multiversx_sdk::wallet::PublicKey; #[test] fn test_private_key_from_mnemonic() { - let mnemonic: Mnemonic = Mnemonic::parse_normalized("acid twice post genre topic observe valid viable gesture fortune funny dawn around blood enemy page update reduce decline van bundle zebra rookie real").unwrap(); + let mnemonic = Mnemonic::parse("acid twice post genre topic observe valid viable gesture fortune funny dawn around blood enemy page update reduce decline van bundle zebra rookie real").unwrap(); - let private_key = Wallet::get_private_key_from_mnemonic(mnemonic.clone(), 0, 0); + let private_key = mnemonic.to_private_key(0, 0).unwrap(); let public_key = PublicKey::from(&private_key); let address = public_key.to_address(); assert_eq!( "0b7966138e80b8f3bb64046f56aea4250fd7bacad6ed214165cea6767fd0bc2c", - private_key.to_string() + private_key.to_seed_hex() ); assert_eq!( "dfefe0453840e5903f2bd519de9b0ed6e9621e57e28ba0b4c1b15115091dd72f", - public_key.to_string() + public_key.to_hex() ); assert_eq!( "erd1mlh7q3fcgrjeq0et65vaaxcw6m5ky8jhu296pdxpk9g32zga6uhsemxx2a", address.to_bech32_default().bech32 ); - let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0, 1); + let private_key = mnemonic.to_private_key(0, 1).unwrap(); let public_key = PublicKey::from(&private_key); let address = public_key.to_address(); assert_eq!( "1648ad209d6b157a289884933e3bb30f161ec7113221ec16f87c3578b05830b0", - private_key.to_string() + private_key.to_seed_hex() ); assert_eq!( "af8fef070a581873912ccbafb6a78bb9eb4e003085ac43dbbdfa3e20eb93cede", - public_key.to_string() + public_key.to_hex() ); assert_eq!( "erd147877pc2tqv88yfvewhmdfuth845uqpsskky8kaalglzp6unem0qpwh982", @@ -40,29 +39,32 @@ fn test_private_key_from_mnemonic() { ); } -#[test] -fn test_load_from_pem() { - let wallet = Wallet::from_pem_file("tests/alice.pem").unwrap(); - let address = wallet.to_address(); - assert_eq!( - "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", - address.to_bech32_default().bech32 - ); -} #[test] fn test_get_shard() { let alice = test_wallets::alice(); // [1, 57, 71, 46, 255, 104, 134, 119, 26, 152, 47, 48, 131, 218, 93, 66, 31, 36, 194, 145, 129, 230, 56, 136, 34, 141, 200, 28, 166, 13, 105, 225] - assert_eq!(0, alice.get_shard()); + assert_eq!(1, alice.address.shard_of_3().as_u32()); let bob = test_wallets::bob(); // [128, 73, 214, 57, 229, 166, 152, 13, 28, 210, 57, 42, 188, 206, 65, 2, 156, 218, 116, 161, 86, 53, 35, 162, 2, 240, 150, 65, 204, 38, 24, 248] - assert_eq!(2, bob.get_shard()); + assert_eq!(0, bob.address.shard_of_3().as_u32()); let carol = test_wallets::carol(); // [178, 161, 21, 85, 206, 82, 30, 73, 68, 224, 154, 177, 117, 73, 216, 91, 72, 125, 205, 38, 200, 75, 80, 23, 163, 158, 49, 163, 103, 8, 137, 186] - assert_eq!(0, carol.get_shard()); + assert_eq!(2, carol.address.shard_of_3().as_u32()); let heidi = test_wallets::heidi(); // [110, 34, 65, 24, 217, 6, 138, 230, 38, 135, 138, 28, 251, 235, 203, 106, 149, 164, 113, 93, 184, 109, 27, 81, 224, 106, 4, 34, 108, 243, 15, 214] - assert_eq!(1, heidi.get_shard()); + assert_eq!(2, heidi.address.shard_of_3().as_u32()); let mike = test_wallets::mike(); // [227, 42, 254, 220, 144, 79, 225, 147, 151, 70, 173, 151, 59, 235, 56, 53, 99, 207, 99, 100, 43, 166, 105, 179, 4, 15, 155, 148, 40, 165, 237, 96] - assert_eq!(0, mike.get_shard()); + assert_eq!(0, mike.address.shard_of_3().as_u32()); + + assert_eq!(0, test_wallets::socrates().address.shard_of_3().as_u32()); + assert_eq!(0, test_wallets::sophocles().address.shard_of_3().as_u32()); + assert_eq!(0, test_wallets::sophie().address.shard_of_3().as_u32()); + + assert_eq!(1, test_wallets::silvia().address.shard_of_3().as_u32()); + assert_eq!(1, test_wallets::siobhan().address.shard_of_3().as_u32()); + assert_eq!(1, test_wallets::simon().address.shard_of_3().as_u32()); + + assert_eq!(2, test_wallets::szidonia().address.shard_of_3().as_u32()); + assert_eq!(2, test_wallets::szilard().address.shard_of_3().as_u32()); + assert_eq!(2, test_wallets::szonja().address.shard_of_3().as_u32()); } diff --git a/sdk/dapp/Cargo.toml b/sdk/dapp/Cargo.toml index a995548506..3dc0354682 100644 --- a/sdk/dapp/Cargo.toml +++ b/sdk/dapp/Cargo.toml @@ -22,7 +22,7 @@ wasm-bindgen-futures = { version = "0.4" } gloo-net = { version = "0.7" } anyhow = "1.0" hex = "0.4" -itertools = "0.14" +itertools = "0.15" log = "0.4" [dependencies.multiversx-sdk] diff --git a/sdk/dapp/src/lib.rs b/sdk/dapp/src/lib.rs index 4ca9648fd7..de17f4742e 100644 --- a/sdk/dapp/src/lib.rs +++ b/sdk/dapp/src/lib.rs @@ -3,4 +3,4 @@ pub use gateway_dapp_proxy::GatewayDappProxy; pub use multiversx_sdk as core; -pub use multiversx_sdk::{data, utils, wallet}; +pub use multiversx_sdk::{data, wallet}; diff --git a/sdk/http/Cargo.toml b/sdk/http/Cargo.toml index 30f5f4878f..03656322ef 100644 --- a/sdk/http/Cargo.toml +++ b/sdk/http/Cargo.toml @@ -23,7 +23,7 @@ tokio = { version = "1.24", features = ["full"] } reqwest = { version = "0.13", features = ["rustls", "blocking", "json"], default-features = false } anyhow = "1.0" hex = "0.4" -itertools = "0.14" +itertools = "0.15" log = "0.4" serde_json = "1.0" diff --git a/sdk/http/examples/sign_tx.rs b/sdk/http/examples/sign_tx.rs index b194680798..9554c1a6fe 100644 --- a/sdk/http/examples/sign_tx.rs +++ b/sdk/http/examples/sign_tx.rs @@ -1,26 +1,28 @@ -use multiversx_sdk::{data::transaction::Transaction, wallet::Wallet}; +use multiversx_sdk::{ + data::transaction::Transaction, + wallet::{PrivateKey, Wallet}, +}; + use multiversx_sdk_http::{DEVNET_GATEWAY, GatewayHttpProxy}; #[tokio::main] -async fn main() { - let wl = Wallet::from_private_key( +async fn main() -> anyhow::Result<()> { + let wallet = Wallet::from(PrivateKey::from_seed_hex_str( "1648ad209d6b157a289884933e3bb30f161ec7113221ec16f87c3578b05830b0", - ) - .unwrap(); - let addr = wl.to_address(); + )?); + let addr = wallet.to_address(); let blockchain = GatewayHttpProxy::new(DEVNET_GATEWAY.to_string()); - let network_config = blockchain.get_network_config().await.unwrap(); + let network_config = blockchain.get_network_config().await?; let arg = blockchain .get_default_transaction_arguments(&addr, &network_config) - .await - .unwrap(); + .await?; let mut unsign_tx = Transaction { nonce: arg.nonce, value: "0".to_string(), - receiver: addr.to_bech32(&network_config.address_hrp), - sender: addr.to_bech32(&network_config.address_hrp), + receiver: addr.to_bech32(network_config.address_hrp), + sender: addr.to_bech32(network_config.address_hrp), gas_price: arg.gas_price, gas_limit: arg.gas_limit, data: arg.data, @@ -28,12 +30,15 @@ async fn main() { chain_id: arg.chain_id, version: arg.version, options: arg.options, + relayer: None, + relayer_signature: None, }; - let signature = wl.sign_tx(&unsign_tx); - unsign_tx.signature = Some(hex::encode(signature)); - let tx_hash = blockchain.send_transaction(&unsign_tx).await.unwrap(); + let signature = wallet.sign_tx(&unsign_tx)?; + unsign_tx.signature = Some(signature); + let tx_hash = blockchain.send_transaction(&unsign_tx).await?; assert!(!tx_hash.is_empty()); println!("tx_hash {tx_hash}"); + Ok(()) } diff --git a/sdk/http/examples/sign_txs.rs b/sdk/http/examples/sign_txs.rs index 13bf767cef..a4348b2910 100644 --- a/sdk/http/examples/sign_txs.rs +++ b/sdk/http/examples/sign_txs.rs @@ -1,51 +1,55 @@ -use multiversx_sdk::{data::transaction::Transaction, wallet::Wallet}; +use multiversx_sdk::{ + data::transaction::{Transaction, TransactionOptions, TransactionVersion}, + wallet::{PrivateKey, Wallet}, +}; use multiversx_sdk_http::{DEVNET_GATEWAY, GatewayHttpProxy}; #[tokio::main] -async fn main() { - let wl = Wallet::from_private_key( +async fn main() -> anyhow::Result<()> { + let wallet = Wallet::from(PrivateKey::from_seed_hex_str( "1648ad209d6b157a289884933e3bb30f161ec7113221ec16f87c3578b05830b0", - ) - .unwrap(); - let addr = wl.to_address(); + )?); + let addr = wallet.to_address(); let blockchain = GatewayHttpProxy::new(DEVNET_GATEWAY.to_string()); - let network_config = blockchain.get_network_config().await.unwrap(); + let network_config = blockchain.get_network_config().await?; let arg = blockchain .get_default_transaction_arguments(&addr, &network_config) - .await - .unwrap(); + .await?; let mut unsign_tx = Transaction { nonce: arg.nonce, value: "1000000000000000000".to_string(), - receiver: addr.to_bech32(&network_config.address_hrp), - sender: addr.to_bech32(&network_config.address_hrp), + receiver: addr.to_bech32(network_config.address_hrp), + sender: addr.to_bech32(network_config.address_hrp), gas_price: arg.gas_price, gas_limit: arg.gas_limit, data: arg.data, signature: None, chain_id: arg.chain_id, - version: arg.version, - options: arg.options, + version: TransactionVersion::V1, + options: None, + relayer: None, + relayer_signature: None, }; let mut txs: Vec = vec![]; - let signature = wl.sign_tx(&unsign_tx); - unsign_tx.signature = Some(hex::encode(signature)); + let signature = wallet.sign_tx(&unsign_tx)?; + unsign_tx.signature = Some(signature); txs.push(unsign_tx.clone()); - unsign_tx.version = 2; - unsign_tx.options = 1; + unsign_tx.version = TransactionVersion::V2; + unsign_tx.options = Some(TransactionOptions::SIGN_WITH_HASH); unsign_tx.nonce += 1; - let signature = wl.sign_tx(&unsign_tx); - unsign_tx.signature = Some(hex::encode(signature)); + let signature = wallet.sign_tx(&unsign_tx)?; + unsign_tx.signature = Some(signature); txs.push(unsign_tx.clone()); - let tx_hash = blockchain.send_transactions(&txs).await.unwrap(); + let tx_hash = blockchain.send_transactions(&txs).await?; assert!(!tx_hash.is_empty()); println!("tx_hashes {tx_hash:?}"); + Ok(()) } diff --git a/sdk/http/examples/tx_cost.rs b/sdk/http/examples/tx_cost.rs index dfdd81b19d..98ea3e7383 100644 --- a/sdk/http/examples/tx_cost.rs +++ b/sdk/http/examples/tx_cost.rs @@ -1,5 +1,6 @@ use multiversx_sdk::{ - chain_core::std::Bech32Address, data::transaction::Transaction, utils::base64_encode, + chain_core::std::{Bech32Address, base64_encode}, + data::transaction::{Transaction, TransactionOptions, TransactionVersion}, }; use multiversx_sdk_http::{DEVNET_GATEWAY, GatewayHttpProxy}; @@ -16,11 +17,13 @@ async fn main() { ), data: Some(base64_encode("hello")), chain_id: "1".to_string(), - version: 1, - options: 0, + version: TransactionVersion::default(), + options: Some(TransactionOptions::default()), gas_limit: 0, gas_price: 0, signature: None, + relayer: None, + relayer_signature: None, }; let blockchain = GatewayHttpProxy::new(DEVNET_GATEWAY.to_string()); diff --git a/sdk/http/src/gateway_http_proxy/http_tx.rs b/sdk/http/src/gateway_http_proxy/http_tx.rs index 4f556944f4..774350aab7 100644 --- a/sdk/http/src/gateway_http_proxy/http_tx.rs +++ b/sdk/http/src/gateway_http_proxy/http_tx.rs @@ -4,7 +4,8 @@ use multiversx_sdk::{ data::{ network_config::NetworkConfig, transaction::{ - ApiTransactionResult, ArgCreateTransaction, Transaction, TxCostResponseData, + ApiTransactionResult, ArgCreateTransaction, Transaction, TransactionOptions, + TransactionVersion, TxCostResponseData, }, vm::{VMQueryInput, VmValuesResponseData}, }, @@ -52,10 +53,10 @@ impl GatewayHttpProxy { network_configs: &NetworkConfig, ) -> Result { let account = self - .get_account(&address.to_bech32(&network_configs.address_hrp)) + .get_account(&address.to_bech32(network_configs.address_hrp)) .await?; - let address_bech32 = address.to_bech32(&network_configs.address_hrp); + let address_bech32 = address.to_bech32(network_configs.address_hrp); Ok(ArgCreateTransaction { nonce: account.nonce, @@ -67,8 +68,8 @@ impl GatewayHttpProxy { data: None, signature: "".to_string(), chain_id: network_configs.chain_id.clone(), - version: network_configs.min_transaction_version, - options: 0, + version: TransactionVersion::default(), + options: Some(TransactionOptions::default()), available_balance: account.balance, }) } diff --git a/sdk/scenario-format/Cargo.toml b/sdk/scenario-format/Cargo.toml index 09b46cdcbe..a5d1a4ab50 100644 --- a/sdk/scenario-format/Cargo.toml +++ b/sdk/scenario-format/Cargo.toml @@ -20,4 +20,4 @@ num-bigint = "0.4" num-traits = "0.2" hex = "0.4" sha3 = "0.12" -bech32 = "0.11" +bech32 = "0.12" diff --git a/tools/cs-test-runner/Cargo.toml b/tools/cs-test-runner/Cargo.toml index 8d7cc4ade3..ff892cb2e9 100644 --- a/tools/cs-test-runner/Cargo.toml +++ b/tools/cs-test-runner/Cargo.toml @@ -7,8 +7,7 @@ authors = ["MultiversX "] [[bin]] name = "cs-test-runner" -path = "src/main.rs" +path = "src/cs_test_runner.rs" [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../framework/meta-lib" +workspace = true diff --git a/tools/cs-test-runner/src/main.rs b/tools/cs-test-runner/src/cs_test_runner.rs similarity index 95% rename from tools/cs-test-runner/src/main.rs rename to tools/cs-test-runner/src/cs_test_runner.rs index b2ae8d33a9..468f1039f9 100644 --- a/tools/cs-test-runner/src/main.rs +++ b/tools/cs-test-runner/src/cs_test_runner.rs @@ -21,10 +21,22 @@ static CS_TESTS: &[CsTest] = &[ package: "multiversx-sc-meta", test_file: "cs_tx_cli_test", }, + // Tests framework functionality directly, no contracts + CsTest { + build_paths: &[], + package: "multiversx-sc-snippets", + test_file: "set_state_cs_test", + }, + // Tests built-in system contracts — no wasm build step required. + CsTest { + build_paths: &[], + package: "system-sc-interact", + test_file: "chain_simulator_token_tests", + }, CsTest { build_paths: &["contracts/examples/adder"], - package: "basic-interactor", - test_file: "basic_interactor_cs_test", + package: "adder-interactor", + test_file: "adder_interactor_cs_test", }, CsTest { build_paths: &["contracts/examples/ping-pong-egld"], @@ -46,12 +58,6 @@ static CS_TESTS: &[CsTest] = &[ package: "payable-interactor", test_file: "payable_interactor_cs_test", }, - // Tests built-in system contracts — no wasm build step required. - CsTest { - build_paths: &[], - package: "system-sc-interact", - test_file: "chain_simulator_token_tests", - }, ]; fn build_contract(path: &Path, cwd: &Path) -> bool { diff --git a/tools/interactor-delegation-func-calls/Cargo.toml b/tools/interactor-delegation-func-calls/Cargo.toml index fe4d31976e..c9937ce580 100644 --- a/tools/interactor-delegation-func-calls/Cargo.toml +++ b/tools/interactor-delegation-func-calls/Cargo.toml @@ -24,8 +24,7 @@ version = "1.0" features = ["derive"] [dependencies.multiversx-sc-snippets] -version = "=0.66.2" -path = "../../framework/snippets" +workspace = true [features] chain-simulator-tests = [] diff --git a/tools/interactor-delegation-func-calls/src/delegation_sc_interact.rs b/tools/interactor-delegation-func-calls/src/delegation_sc_interact.rs index a810801c15..0dbc24fb4e 100644 --- a/tools/interactor-delegation-func-calls/src/delegation_sc_interact.rs +++ b/tools/interactor-delegation-func-calls/src/delegation_sc_interact.rs @@ -10,7 +10,7 @@ use delegation_sc_interact_state::State; use multiversx_sc_snippets::{ imports::*, - sdk::{gateway::SetStateAccount, utils::base64_decode}, + sdk::{chain_core::std::base64_decode, gateway::SetStateAccount}, }; pub async fn delegation_sc_interact_cli() { @@ -134,9 +134,8 @@ impl DelegateCallsInteract { pub async fn new(config: Config) -> Self { let mut interactor = Interactor::new(config.gateway_uri()) .await - .use_chain_simulator(config.is_chain_simulator()); - - interactor.set_current_dir_from_workspace("tools/interactor-delegation-func-calls"); + .use_chain_simulator(config.is_chain_simulator()) + .with_current_dir(env!("CARGO_MANIFEST_DIR")); let wallet_address = interactor.register_wallet(test_wallets::alice()).await; let delegator1 = interactor.register_wallet(test_wallets::bob()).await; let delegator2 = interactor.register_wallet(test_wallets::dan()).await; @@ -197,7 +196,7 @@ impl DelegateCallsInteract { .find(|log| log.endpoint == "SCDeploy") .expect("No SCDeploy log found"); - let decode_deploy_address = base64_decode(deploy_log.topics[0].clone()); + let decode_deploy_address = base64_decode(deploy_log.topics[0].clone()).unwrap(); let deploy_address = Bech32Address::from(Address::from_slice(&decode_deploy_address)); self.state.set_delegation_address(deploy_address); diff --git a/tools/interactor-governance-func-calls/Cargo.toml b/tools/interactor-governance-func-calls/Cargo.toml index 82eba77f29..3eb54f9f91 100644 --- a/tools/interactor-governance-func-calls/Cargo.toml +++ b/tools/interactor-governance-func-calls/Cargo.toml @@ -24,8 +24,7 @@ version = "1.0" features = ["derive"] [dependencies.multiversx-sc-snippets] -version = "=0.66.2" -path = "../../framework/snippets" +workspace = true [dependencies.delegation-sc-interact] path = "../interactor-delegation-func-calls" diff --git a/tools/interactor-governance-func-calls/src/governance_sc_interact.rs b/tools/interactor-governance-func-calls/src/governance_sc_interact.rs index 57e81a5a0a..62e726d055 100644 --- a/tools/interactor-governance-func-calls/src/governance_sc_interact.rs +++ b/tools/interactor-governance-func-calls/src/governance_sc_interact.rs @@ -8,7 +8,7 @@ use governance_sc_interact_state::State; use multiversx_sc_snippets::{ imports::*, - sdk::{gateway::SetStateAccount, utils::base64_decode}, + sdk::{chain_core::std::base64_decode, gateway::SetStateAccount}, }; pub async fn governance_sc_interact_cli() { @@ -89,9 +89,8 @@ impl GovernanceCallsInteract { pub async fn new(config: Config) -> Self { let mut interactor = Interactor::new(config.gateway_uri()) .await - .use_chain_simulator(config.is_chain_simulator()); - - interactor.set_current_dir_from_workspace("tools/interactor-governance-func-calls"); + .use_chain_simulator(config.is_chain_simulator()) + .with_current_dir(env!("CARGO_MANIFEST_DIR")); let owner = interactor.register_wallet(test_wallets::eve()).await; let user1 = interactor.register_wallet(test_wallets::mike()).await; let user2 = interactor.register_wallet(test_wallets::judy()).await; diff --git a/tools/interactor-system-func-calls/Cargo.toml b/tools/interactor-system-func-calls/Cargo.toml index e94744f286..18d7621a0d 100644 --- a/tools/interactor-system-func-calls/Cargo.toml +++ b/tools/interactor-system-func-calls/Cargo.toml @@ -25,8 +25,7 @@ version = "1.0" features = ["derive"] [dependencies.multiversx-sc-snippets] -version = "=0.66.2" -path = "../../framework/snippets" +workspace = true [features] chain-simulator-tests = [] diff --git a/tools/interactor-system-func-calls/src/system_sc_interact.rs b/tools/interactor-system-func-calls/src/system_sc_interact.rs index e81f1796b2..756ca6b8f5 100644 --- a/tools/interactor-system-func-calls/src/system_sc_interact.rs +++ b/tools/interactor-system-func-calls/src/system_sc_interact.rs @@ -11,7 +11,7 @@ use system_sc_interact_state::State; use multiversx_sc_snippets::{ imports::*, - sdk::{data::esdt::EsdtBalance, utils::base64_decode}, + sdk::{chain_core::std::base64_decode, data::esdt::EsdtBalance}, }; pub async fn system_sc_interact_cli() { @@ -238,9 +238,8 @@ impl SysFuncCallsInteract { pub async fn init(config: Config) -> Self { let mut interactor = Interactor::new(config.gateway_uri()) .await - .use_chain_simulator(config.is_chain_simulator()); - - interactor.set_current_dir_from_workspace("tools/interactor-system-func-calls"); + .use_chain_simulator(config.is_chain_simulator()) + .with_current_dir(env!("CARGO_MANIFEST_DIR")); let wallet_address = interactor.register_wallet(test_wallets::carol()).await; let other_wallet_address = interactor.register_wallet(test_wallets::carol()).await; @@ -1029,7 +1028,7 @@ impl SysFuncCallsInteract { assert_eq!(expected_uris.len(), uris.len()); for (index, uri) in uris.iter().enumerate() { - let uri_string = String::from_utf8(base64_decode(uri)).unwrap(); + let uri_string = String::from_utf8(base64_decode(uri).unwrap()).unwrap(); assert_eq!(expected_uris[index], uri_string); } } diff --git a/tools/managed-mem-bench/Cargo.toml b/tools/managed-mem-bench/Cargo.toml index fde20c6b11..320257ecec 100644 --- a/tools/managed-mem-bench/Cargo.toml +++ b/tools/managed-mem-bench/Cargo.toml @@ -13,9 +13,7 @@ name = "bench-threading" path = "src/bench_threading.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../framework/base" +workspace = true [dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../framework/scenario" +workspace = true diff --git a/tools/mxpy-snippet-generator/Cargo.toml b/tools/mxpy-snippet-generator/Cargo.toml index 6d6ca7d502..181b80a176 100644 --- a/tools/mxpy-snippet-generator/Cargo.toml +++ b/tools/mxpy-snippet-generator/Cargo.toml @@ -10,11 +10,10 @@ name = "mxpy-snippet-generator" path = "src/mxpy_snippet_generator.rs" [dependencies.multiversx-sc] -version = "0.66.2" -path = "../../framework/base" +workspace = true [dependencies] -bech32 = "0.11" +bech32 = "0.12" num-bigint = "0.4" num-traits = "0.2" hex = "0.4" diff --git a/tools/op-test-gen/Cargo.toml b/tools/op-test-gen/Cargo.toml index c04e517ecf..963ded7c5e 100644 --- a/tools/op-test-gen/Cargo.toml +++ b/tools/op-test-gen/Cargo.toml @@ -17,5 +17,4 @@ num-bigint = "0.4" num-traits = "0.2" [dependencies.multiversx-sc-scenario] -version = "0.66.2" -path = "../../framework/scenario" +workspace = true diff --git a/tools/rust-debugger/format-tests/Cargo.lock b/tools/rust-debugger/format-tests/Cargo.lock index 921efa8301..1b61dcdc33 100644 --- a/tools/rust-debugger/format-tests/Cargo.lock +++ b/tools/rust-debugger/format-tests/Cargo.lock @@ -54,21 +54,27 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -84,9 +90,9 @@ checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "blake2" @@ -108,18 +114,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "cfg-if" @@ -129,9 +135,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -305,7 +311,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -358,12 +364,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.4.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" dependencies = [ "rustversion", "typenum", @@ -414,16 +414,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] @@ -432,22 +430,13 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "foldhash 0.2.0", + "foldhash", "serde", "serde_core", ] @@ -479,12 +468,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "indexmap" version = "2.14.0" @@ -492,7 +475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown", "serde", "serde_core", ] @@ -548,23 +531,25 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "multiversx-chain-core" -version = "0.23.0" +version = "0.23.1" dependencies = [ + "base64", "bech32", "bitflags", "blake2", + "ed25519-dalek", "hex", "multiversx-sc-codec", "serde", @@ -587,12 +572,11 @@ dependencies = [ [[package]] name = "multiversx-chain-vm" -version = "0.23.0" +version = "0.23.1" dependencies = [ "anyhow", "bitflags", "colored", - "ed25519-dalek", "hex", "itertools", "log", @@ -617,10 +601,10 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.66.0" +version = "0.66.2" dependencies = [ "bitflags", - "generic-array 1.4.1", + "generic-array 1.4.3", "multiversx-chain-core", "multiversx-sc-codec", "multiversx-sc-derive", @@ -652,7 +636,7 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.66.0" +version = "0.66.2" dependencies = [ "hex", "proc-macro2", @@ -663,7 +647,7 @@ dependencies = [ [[package]] name = "multiversx-sc-meta-lib" -version = "0.66.0" +version = "0.66.2" dependencies = [ "clap", "colored", @@ -684,7 +668,7 @@ dependencies = [ [[package]] name = "multiversx-sc-scenario" -version = "0.66.0" +version = "0.66.2" dependencies = [ "colored", "hex", @@ -764,16 +748,6 @@ dependencies = [ "spki", ] -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -785,9 +759,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -815,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -896,9 +870,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -966,9 +940,9 @@ checksum = "69da7c8ef9e55986dcaa55dd095bbf7d321e80cc91644f25ce26a83dbe9e7f14" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spki" @@ -1000,9 +974,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -1059,9 +1033,9 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1071,9 +1045,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -1081,12 +1055,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "unwrap-infallible" version = "1.0.0" @@ -1111,79 +1079,38 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - [[package]] name = "wasm-encoder" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69830ccbbf41c55eb585991659fb70867ef628193af3a495f09a6956f7615e59" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.249.0", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", + "wasmparser 0.252.0", ] [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.249.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" dependencies = [ "bitflags", - "hashbrown 0.15.5", + "hashbrown", "indexmap", "semver", + "serde", ] [[package]] name = "wasmparser" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" dependencies = [ "bitflags", - "hashbrown 0.17.1", "indexmap", "semver", - "serde", ] [[package]] @@ -1199,22 +1126,22 @@ dependencies = [ [[package]] name = "wast" -version = "249.0.0" +version = "252.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2474a321bf9ae2808e9fa23ac4ec2b27300e70985e30bcb5a38d43b76bfc901a" +checksum = "942a3449d6a593fccc111a6241c8df52bda168af30e40bf9580d4394d7374c65" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.249.0", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.249.0" +version = "1.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28af699d0a9c7e4e250b7b8e36167ae5215fbb4b7ae526bb4ce7b234ba0afc90" +checksum = "c72a4ba7088f7bac94cf516e49882bdf97068904a563768cf249efc839ec42cb" dependencies = [ "wast", ] @@ -1249,105 +1176,11 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", -] - [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zmij" diff --git a/tools/strip-path/src/main.rs b/tools/strip-path/src/main.rs index dbee2ada0e..f200d8dbdb 100644 --- a/tools/strip-path/src/main.rs +++ b/tools/strip-path/src/main.rs @@ -1,7 +1,13 @@ +use multiversx_sc_meta::cargo_toml::WorkspaceDependencies; use multiversx_sc_meta::folder_structure::strip_path; use std::path::Path; fn main() { let root = std::env::args().nth(1).unwrap_or_else(|| ".".to_string()); - strip_path(Path::new(&root), &["target".to_string()]); + let workspace_dependencies = WorkspaceDependencies::find_from_dir(&root).unwrap_or_default(); + strip_path( + Path::new(&root), + &["target".to_string()], + &workspace_dependencies, + ); } diff --git a/tools/wallet-miner/Cargo.toml b/tools/wallet-miner/Cargo.toml new file mode 100644 index 0000000000..3713e1d493 --- /dev/null +++ b/tools/wallet-miner/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wallet-miner" +version = "0.1.0" +edition = "2024" +publish = false + +description = "Mines wallets with selected hex address suffixes" + +[[bin]] +name = "wallet-miner" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0" + +[dependencies.multiversx-sc-meta] +version = "=0.66.2" +path = "../../framework/meta" + +[dependencies.multiversx-sdk] +version = "=0.16.1" +path = "../../sdk/core" diff --git a/tools/wallet-miner/src/main.rs b/tools/wallet-miner/src/main.rs new file mode 100644 index 0000000000..1c88ce873c --- /dev/null +++ b/tools/wallet-miner/src/main.rs @@ -0,0 +1,63 @@ +#![allow(clippy::manual_is_multiple_of)] + +use std::{ + fs::File, + io::{self, Write}, +}; + +use anyhow::Result; +use multiversx_sc_meta::cmd::wallet_cmd::generate_mnemonic; +use multiversx_sdk::{chain_core::std::Bech32Hrp, wallet::Wallet}; + +const SUFFIXES: [&str; 3] = ["0000", "0001", "0002"]; + +fn main() -> Result<()> { + let hrp = Bech32Hrp::default(); + let mut mined_count = 0usize; + let mut retries = 0u64; + + loop { + let mnemonic = generate_mnemonic(); + let wallet = Wallet::try_from(mnemonic.clone())?; + let address_hex = wallet.address.to_hex(); + + for suffix in SUFFIXES { + if !address_hex.ends_with(suffix) { + continue; + } + + mined_count += 1; + + if retries >= 1000 { + println!("\rMining suffixes: found 0x{suffix} after {retries} retries"); + } + + println!("Mined wallet ending in 0x{suffix}:"); + println!("Mnemonic: {}", mnemonic); + println!("Wallet address:"); + println!(" - bech32: {}", wallet.address.to_bech32(hrp)); + println!(" - hex: 0x{address_hex}"); + + let pem_content = wallet.to_pem(hrp)?.to_pem_str(); + write_pem(suffix, &address_hex, pem_content)?; + + println!(); + } + + retries += 1; + if retries % 100 == 0 { + print!("\rMining suffixes forever: {retries} retries, {mined_count} wallets mined"); + io::stdout().flush()?; + } + } +} + +fn write_pem(suffix: &str, address_hex: &str, contents: String) -> Result<()> { + let dir = env!("CARGO_MANIFEST_DIR"); + let outfile = format!("{dir}/wallet-{suffix}-{address_hex}.pem"); + let mut file = File::create(&outfile)?; + file.write_all(contents.as_bytes())?; + println!("Wallet saved to '{outfile}'"); + + Ok(()) +} diff --git a/tools/wallet-miner/wallet-0000-1ea73a6477ab57d4fc3e2b0ec45a8831202cfb691ec8088cf09e6b6f159f0000.pem b/tools/wallet-miner/wallet-0000-1ea73a6477ab57d4fc3e2b0ec45a8831202cfb691ec8088cf09e6b6f159f0000.pem new file mode 100644 index 0000000000..4e7c94fb27 --- /dev/null +++ b/tools/wallet-miner/wallet-0000-1ea73a6477ab57d4fc3e2b0ec45a8831202cfb691ec8088cf09e6b6f159f0000.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1r6nn5erh4dtaflp79v8vgk5gxysze7mfrmyq3r8sne4k79vlqqqq65skcs----- +ZGUyMzBiYTNhMzAzZmY3MzQwYTlhOTNjOWQ5YzUyZmVkNmQ4ODE4MDA2YjFiMDli +YTY3NjRhYWM2ZDM1ODc3YTFlYTczYTY0NzdhYjU3ZDRmYzNlMmIwZWM0NWE4ODMx +MjAyY2ZiNjkxZWM4MDg4Y2YwOWU2YjZmMTU5ZjAwMDA= +-----END PRIVATE KEY for erd1r6nn5erh4dtaflp79v8vgk5gxysze7mfrmyq3r8sne4k79vlqqqq65skcs----- diff --git a/tools/wallet-miner/wallet-0000-4ddee4dc8db85c7fc1528912c0ef61839cbc0fe22fc9ae4a4172b27bf4370000.pem b/tools/wallet-miner/wallet-0000-4ddee4dc8db85c7fc1528912c0ef61839cbc0fe22fc9ae4a4172b27bf4370000.pem new file mode 100644 index 0000000000..f97b1671dc --- /dev/null +++ b/tools/wallet-miner/wallet-0000-4ddee4dc8db85c7fc1528912c0ef61839cbc0fe22fc9ae4a4172b27bf4370000.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1fh0wfhydhpw8ls2j3yfvpmmpswwtcrlz9ly6ujjpw2e8haphqqqq33dy5u----- +MTUwNDdmMGMyYWYwMTVmZTQ2MTM3YzJlYWU1MTMyM2VmNDAyN2YyOWI5MzJkYWQ5 +MzA5MDZjNDY1YmE2OGEyNjRkZGVlNGRjOGRiODVjN2ZjMTUyODkxMmMwZWY2MTgz +OWNiYzBmZTIyZmM5YWU0YTQxNzJiMjdiZjQzNzAwMDA= +-----END PRIVATE KEY for erd1fh0wfhydhpw8ls2j3yfvpmmpswwtcrlz9ly6ujjpw2e8haphqqqq33dy5u----- diff --git a/tools/wallet-miner/wallet-0001-35c911a8f698db378ed3abcfb356bf323937042c8686fe27861c0501e38f0001.pem b/tools/wallet-miner/wallet-0001-35c911a8f698db378ed3abcfb356bf323937042c8686fe27861c0501e38f0001.pem new file mode 100644 index 0000000000..07e925c271 --- /dev/null +++ b/tools/wallet-miner/wallet-0001-35c911a8f698db378ed3abcfb356bf323937042c8686fe27861c0501e38f0001.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1xhy3r28knrdn0rkn408mx44lxgunwppvs6r0ufuxrszsrcu0qqqsay75y8----- +NWM0MTA5Njk4Mjk5YTdjNDc5Y2I4YzBhZWQ4MjAxOWIzY2IxYmFjNjUyMTdmN2Ez +YjBlNWRhOGJjYzk4YzljNjM1YzkxMWE4ZjY5OGRiMzc4ZWQzYWJjZmIzNTZiZjMy +MzkzNzA0MmM4Njg2ZmUyNzg2MWMwNTAxZTM4ZjAwMDE= +-----END PRIVATE KEY for erd1xhy3r28knrdn0rkn408mx44lxgunwppvs6r0ufuxrszsrcu0qqqsay75y8----- diff --git a/tools/wallet-miner/wallet-0001-cfc162090cc8420c78150bd0ad75cd08653e2af110579d6e564f4726d3f70001.pem b/tools/wallet-miner/wallet-0001-cfc162090cc8420c78150bd0ad75cd08653e2af110579d6e564f4726d3f70001.pem new file mode 100644 index 0000000000..a5609aace0 --- /dev/null +++ b/tools/wallet-miner/wallet-0001-cfc162090cc8420c78150bd0ad75cd08653e2af110579d6e564f4726d3f70001.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1elqkyzgveppqc7q4p0g26awdppjnu2h3zpte6mjkfarjd5lhqqqsmyk8vf----- +Mjc0Y2ZiMTM2Yjg4ZTUwZjE0OTFkNThmY2EwZGJmOWQzMGMzZGVhMGNjMDI3ZjM4 +MDQzYWRjOGE2NTQyNDkxZmNmYzE2MjA5MGNjODQyMGM3ODE1MGJkMGFkNzVjZDA4 +NjUzZTJhZjExMDU3OWQ2ZTU2NGY0NzI2ZDNmNzAwMDE= +-----END PRIVATE KEY for erd1elqkyzgveppqc7q4p0g26awdppjnu2h3zpte6mjkfarjd5lhqqqsmyk8vf----- diff --git a/tools/wat-gen/Cargo.toml b/tools/wat-gen/Cargo.toml index 3fc3dd3342..20c31c2a75 100644 --- a/tools/wat-gen/Cargo.toml +++ b/tools/wat-gen/Cargo.toml @@ -14,5 +14,4 @@ path = "src/wat_gen_main.rs" wat = "1.235" [dependencies.multiversx-sc-meta-lib] -version = "0.66.2" -path = "../../framework/meta-lib" +workspace = true