diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 91a510c19..9e064d76b 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -22,7 +22,7 @@ jobs: name: Contracts uses: multiversx/mx-sc-actions/.github/workflows/contracts.yml@v4.2.2 with: - rust-toolchain: stable + rust-toolchain: 1.87 coverage-args: --ignore-filename-regex='/.cargo/git' --output ./coverage.md enable-interactor-tests: true secrets: diff --git a/.github/workflows/on_pull_request_build_contracts.yml b/.github/workflows/on_pull_request_build_contracts.yml index 9f6a3f343..f6e353967 100644 --- a/.github/workflows/on_pull_request_build_contracts.yml +++ b/.github/workflows/on_pull_request_build_contracts.yml @@ -10,4 +10,4 @@ jobs: build: uses: multiversx/mx-sc-actions/.github/workflows/reproducible-build.yml@v4.2.2 with: - image_tag: v10.0.0 \ No newline at end of file + image_tag: v10.0.0 diff --git a/.github/workflows/proxy-compare.yml b/.github/workflows/proxy-compare.yml index 3ee721303..b81a2d0fb 100644 --- a/.github/workflows/proxy-compare.yml +++ b/.github/workflows/proxy-compare.yml @@ -17,8 +17,7 @@ jobs: - name: Install rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: - default: true - toolchain: stable + toolchain: 1.87 target: wasm32-unknown-unknown - name: Install prerequisites diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..855f588fe --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Repository Guidelines + +## Project Structure & Module Organization +Each MultiversX smart contract lives in its own crate (e.g. `mvx-fee-market`, `sov-esdt-safe`, `header-verifier`) with logic in `src/` and generated artifacts in `wasm/` and `output/`. Shared crates in `common/` (`common-utils`, `structs`, `fee-common`, etc.) feed reusable modules, meta crates (`*/meta`) emit ABI and deployable bundles, `interactor/` hosts integration flows, and `chain-config/` keeps scenario tests and deployment presets aligned. + +## Build, Test, and Development Commands +Build and compile the smart contracts with `sc-meta all build`, which refreshes `multiversx.json` and the matching `output/` bundle. Execute blackbox tests with `sc-meta test` either inside one contract crate or at the root of the repo. Simulator interaction flows follow `interactor/HowToRun.md`: start `sc-meta cs start`, delete stale `state.toml`, run the bootstrap deployment (`cargo test --package rust-interact --test always_deploy_setup_first --all-features -- deploy_setup --exact --show-output`), then execute focused scenarios. + +## Coding Style & Naming Conventions +Follow rustfmt defaults (4-space indentation, trailing commas) and run `cargo fmt --all` before submitting changes. Contract modules and files use `snake_case`, traits stay in `UpperCamelCase`, and constants follow the MultiversX screaming-snake style (`ESDT_SAFE_ADDRESS_NOT_SET`). Lint with `cargo clippy --workspace --all-targets -- -D warnings` to keep endpoint traits and storage modules aligned. + +## Testing Guidelines +Prefer `multiversx-sc-scenario` tests for endpoint coverage, naming them after the contract and behavior (e.g. `fee_market_complete_setup.rs`). Seed fixtures through `common-test-setup` helpers to keep any contract interaction easy to use across all the smart contracts. For simulator-backed tests, follow the bootstrap steps in `interactor/HowToRun.md` so the common state is seeded before running per-file suites. Include negative-path assertions for guard checks and document any skipped cases inline. When touching deployment presets, rerun `chain-config/tests` to confirm serialized output still mirrors `sc-config.toml`. + +## Commit & Pull Request Guidelines +CRITICAL: DO NOT COMMIT ANYTHING YOURSELF diff --git a/Cargo.lock b/Cargo.lock index 8947874cd..76e5b13fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -37,11 +37,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -54,44 +60,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arrayvec" @@ -99,17 +105,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -134,9 +146,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bech32" @@ -146,13 +158,13 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bip39" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -175,9 +187,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block-buffer" @@ -190,9 +202,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -200,30 +212,44 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" -version = "1.2.18" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chain-config" version = "0.1.0" dependencies = [ + "common-test-setup", + "common-utils", + "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", + "proxies", + "setup-phase", + "structs", ] [[package]] @@ -239,11 +265,16 @@ name = "chain-factory" version = "0.1.0" dependencies = [ "chain-config", + "common-test-setup", + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "multiversx-sc-scenario", "num-bigint", - "utils", + "proxies", + "structs", ] [[package]] @@ -254,6 +285,33 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -266,9 +324,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" dependencies = [ "clap_builder", "clap_derive", @@ -276,9 +334,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" dependencies = [ "anstream", "anstyle", @@ -288,9 +346,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -300,15 +358,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -319,21 +377,58 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "common-interactor" +version = "0.1.0" +dependencies = [ + "base64 0.21.7", + "common-test-setup", + "error-messages", + "multiversx-bls", + "multiversx-sc", + "multiversx-sc-snippets", + "proxies", + "rand 0.9.2", + "serde", + "structs", + "toml 0.8.23", +] + [[package]] name = "common-test-setup" version = "0.1.0" dependencies = [ + "base64 0.21.7", "chain-config", + "chain-factory", "cross-chain", - "fee-market", + "error-messages", "header-verifier", + "hex", + "multiversx-sc", "multiversx-sc-scenario", "mvx-esdt-safe", + "mvx-fee-market", "proxies", + "rand 0.8.5", + "rand_core 0.6.4", + "sov-fee-market", + "sovereign-forge", "structs", "testing-sc", ] +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -342,9 +437,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +checksum = "db05ffb6856bf0ecdf6367558a76a0e8a77b1713044eb92845c692100ed50190" dependencies = [ "unicode-segmentation", ] @@ -374,18 +469,83 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "cross-chain" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "proxies", "structs", - "utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -432,11 +592,20 @@ dependencies = [ "syn", ] +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", +] + [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -476,9 +645,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -496,9 +665,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "env_filter" @@ -531,12 +700,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -550,24 +719,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "fee-market" +name = "fee-common" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", - "multiversx-sc-scenario", - "num-bigint", + "multiversx-sc-modules", "proxies", "structs", - "utils", -] - -[[package]] -name = "fee-market-meta" -version = "0.1.0" -dependencies = [ - "fee-market", - "multiversx-sc-meta-lib", ] [[package]] @@ -576,6 +737,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "fnv" version = "1.0.7" @@ -605,9 +772,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -683,6 +850,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -722,25 +895,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -749,11 +922,27 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", "serde", @@ -764,11 +953,14 @@ name = "header-verifier" version = "0.1.0" dependencies = [ "common-test-setup", + "common-utils", "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-scenario", "proxies", + "setup-phase", "structs", ] @@ -800,9 +992,9 @@ checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "hmac" @@ -855,18 +1047,20 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "http", "http-body", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -890,17 +1084,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -910,21 +1108,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -933,31 +1132,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -965,72 +1144,59 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1039,9 +1205,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1049,13 +1215,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" dependencies = [ "equivalent", "hashbrown", "serde", + "serde_core", ] [[package]] @@ -1067,18 +1234,48 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1096,9 +1293,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f33145a5cbea837164362c7bd596106eb7c5198f97d1ba6f6ebb3223952e488" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -1109,9 +1306,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ce13c40ec6956157a3635d97a1ee2df323b263f09ea14165131289cb0f5c19" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", @@ -1120,9 +1317,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" dependencies = [ "once_cell", "wasm-bindgen", @@ -1151,27 +1348,27 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1179,57 +1376,64 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "multiversx-bls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1806bbba4570d616d34b5f042ae9d7fe7aad5d29e50dd14e90a51d4cd5ac868" +dependencies = [ + "criterion", + "hex", ] [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ + "bech32", "bitflags", + "hex", "multiversx-sc-codec", + "serde", ] [[package]] name = "multiversx-chain-scenario-format" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e688a0d32a5873871cd19224186ecbfaa0b26f7c8ec62f0ca35d7ee9d4616b40" +checksum = "95f641b1c2526917621136b1d607eb5ff523121b7a48ed784ed5570d7aa8f90c" dependencies = [ "bech32", "hex", @@ -1242,37 +1446,46 @@ dependencies = [ [[package]] name = "multiversx-chain-vm" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9e0408c341bbd1d73365b2dd8a96b0c2a41f9458d3dc3519f64275754e7971" +checksum = "2a4f526b2a0aea3dfd02a12b1ada57915778ef4f046679a817632e5aa54d6719" dependencies = [ + "anyhow", "bitflags", "colored", "ed25519-dalek", "hex", "hex-literal", - "itertools", + "itertools 0.14.0", + "log", + "multiversx-bls", "multiversx-chain-core", "multiversx-chain-vm-executor", "num-bigint", "num-traits", - "rand", + "rand 0.8.5", "rand_seeder", + "serde", "sha2", "sha3", + "toml 0.9.6", ] [[package]] name = "multiversx-chain-vm-executor" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51cce7ae386960fbf5e85afe40ca16d63f926f0620ed1a36b019212b28e17219" - -[[package]] +checksum = "0cfb36050709604abee7d7896ad890358692245ddd406adcbce9d717b06c6cdf" +dependencies = [ + "serde", + "toml 0.7.8", +] + +[[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array 1.2.0", @@ -1286,9 +1499,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -1299,9 +1512,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -1311,9 +1524,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -1324,9 +1537,9 @@ dependencies = [ [[package]] name = "multiversx-sc-meta-lib" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "438149f455382e64d1d383b2cb5bee144c215d2943d6a0ae06a50d3225680fbb" +checksum = "fee6cb7d34198c01708963601e528bf16accffaa4598f56c99befad89f17bfcf" dependencies = [ "clap", "colored", @@ -1338,32 +1551,31 @@ dependencies = [ "semver", "serde", "serde_json", - "toml", - "wasmparser 0.227.1", + "toml 0.9.6", + "wasmparser 0.241.2", "wasmprinter", "wat", ] [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-scenario" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a96dfc78030fec0f05928d2fa0334d1eeaaac2df3b819182876e402461e8c7e" +checksum = "db0895d915a6f50e9b84eaf6c2942e0d64f09f7f385777beb697a52877e2ebfa" dependencies = [ "base64 0.22.1", - "bech32", "colored", "hex", - "itertools", + "itertools 0.14.0", "log", "multiversx-chain-scenario-format", "multiversx-chain-vm", @@ -1376,17 +1588,19 @@ dependencies = [ "serde", "serde_json", "sha2", + "simple-error", "unwrap-infallible", ] [[package]] name = "multiversx-sc-snippets" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ca4f5122f52acf754ce563e847554140be333d1edd5aac64500faaa91384e9" +checksum = "8d87e6cc5bcb76e3dabb346d3d0a8e764f44ea9f273ccc64f4c7d432d4c3fbc7" dependencies = [ "anyhow", "base64 0.22.1", + "colored", "env_logger", "futures", "hex", @@ -1401,24 +1615,23 @@ dependencies = [ [[package]] name = "multiversx-sdk" -version = "0.9.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be4179fcdc1b1028c900e9a15f97035553da005aac4b92d23f0c6eff4904fd6" +checksum = "a2d7679b1e615ddc954732d78711f20a2280d111f092fa1b64e6336c13c87aea" dependencies = [ "aes", "anyhow", "base64 0.22.1", - "bech32", "bip39", "ctr", "hex", "hmac", - "itertools", + "itertools 0.14.0", "log", "multiversx-chain-core", "pbkdf2", "pem", - "rand", + "rand 0.8.5", "scrypt", "serde", "serde_json", @@ -1431,16 +1644,17 @@ dependencies = [ [[package]] name = "multiversx-sdk-http" -version = "0.9.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b177f1959609a5dcc166474ae860824f213cf141484a6d82455df1169c3448" +checksum = "f4bb8bc486528f8b58646b9628194d5163ad106f554c54960043a5ea70b48c16" dependencies = [ "anyhow", "hex", - "itertools", + "itertools 0.14.0", "log", "multiversx-sdk", "reqwest", + "serde_json", "tokio", ] @@ -1450,18 +1664,19 @@ version = "0.1.0" dependencies = [ "chain-config", "common-test-setup", + "common-utils", "cross-chain", + "custom-events", "error-messages", - "fee-market", "header-verifier", "multiversx-sc", "multiversx-sc-modules", "multiversx-sc-scenario", + "mvx-fee-market", "proxies", "setup-phase", "structs", "testing-sc", - "utils", ] [[package]] @@ -1472,6 +1687,31 @@ dependencies = [ "mvx-esdt-safe", ] +[[package]] +name = "mvx-fee-market" +version = "0.1.0" +dependencies = [ + "common-test-setup", + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "multiversx-sc-scenario", + "num-bigint", + "proxies", + "setup-phase", + "structs", +] + +[[package]] +name = "mvx-fee-market-meta" +version = "0.1.0" +dependencies = [ + "multiversx-sc-meta-lib", + "mvx-fee-market", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -1541,11 +1781,23 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", @@ -1575,9 +1827,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -1587,9 +1839,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1597,9 +1849,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1615,7 +1867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1647,9 +1899,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -1679,11 +1931,39 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -1694,6 +1974,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1703,11 +1992,20 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.5", +] + [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1722,24 +2020,24 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -1752,8 +2050,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1763,7 +2071,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1772,7 +2090,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -1781,23 +2108,43 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9febe641d2842ffc76ee962668a17578767c4e01735e4802b21ed9a24b2e4e" dependencies = [ - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", @@ -1807,9 +2154,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", @@ -1818,15 +2165,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "relative-path" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64 0.22.1", "bytes", @@ -1839,15 +2192,12 @@ dependencies = [ "hyper", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -1855,39 +2205,72 @@ dependencies = [ "tokio", "tokio-native-tls", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", +] + +[[package]] +name = "rstest" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", ] [[package]] name = "rust-interact" version = "0.1.0" dependencies = [ - "base64 0.21.7", "clap", + "common-interactor", "common-test-setup", + "cross-chain", "error-messages", "header-verifier", "multiversx-sc", + "multiversx-sc-scenario", "multiversx-sc-snippets", "mvx-esdt-safe", "proxies", + "rstest", "serde", "serial_test", "sov-esdt-safe", "structs", - "toml", + "toml 0.8.23", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" @@ -1900,37 +2283,31 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" - [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1947,22 +2324,31 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scc" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea091f6cac2595aa38993f04f4ee692ed43757035c36e67c180b6828356385b1" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -1985,9 +2371,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.8" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "security-framework" @@ -2004,9 +2390,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -2014,24 +2400,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", @@ -2040,15 +2436,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "indexmap", "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2064,13 +2461,22 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2112,6 +2518,8 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-scenario", @@ -2119,9 +2527,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -2146,9 +2554,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -2159,32 +2567,35 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "simple-error" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2accd2c41a0e920d2abd91b2badcfa1da784662f54fbc47e0e3a51f1e2e1cf" + [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2192,17 +2603,20 @@ name = "sov-esdt-safe" version = "0.1.0" dependencies = [ "common-test-setup", + "common-utils", "cross-chain", + "custom-events", "error-messages", - "fee-market", + "fee-common", "multiversx-sc", "multiversx-sc-modules", "multiversx-sc-scenario", + "mvx-fee-market", "proxies", "setup-phase", "structs", "testing-sc", - "utils", + "tx-nonce", ] [[package]] @@ -2213,6 +2627,61 @@ dependencies = [ "sov-esdt-safe", ] +[[package]] +name = "sov-fee-market" +version = "0.1.0" +dependencies = [ + "common-test-setup", + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "multiversx-sc-scenario", + "num-bigint", + "proxies", + "structs", +] + +[[package]] +name = "sov-fee-market-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "sov-fee-market", +] + +[[package]] +name = "sovereign-forge" +version = "0.0.0" +dependencies = [ + "chain-config", + "chain-factory", + "common-test-setup", + "common-utils", + "cross-chain", + "custom-events", + "error-messages", + "fee-common", + "header-verifier", + "multiversx-sc", + "multiversx-sc-modules", + "multiversx-sc-scenario", + "mvx-esdt-safe", + "mvx-fee-market", + "num-bigint", + "proxies", + "structs", +] + +[[package]] +name = "sovereign-forge-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "sovereign-forge", +] + [[package]] name = "spki" version = "0.7.3" @@ -2250,9 +2719,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2270,9 +2739,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -2281,15 +2750,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] @@ -2308,6 +2777,7 @@ dependencies = [ "multiversx-sc", "multiversx-sc-scenario", "num-bigint", + "proxies", ] [[package]] @@ -2320,19 +2790,29 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -2343,33 +2823,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "token-whitelist" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "multiversx-sc-scenario", - "setup-phase", - "utils", -] - [[package]] name = "tokio" -version = "1.44.2" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2395,39 +2866,121 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" dependencies = [ "indexmap", + "serde_core", + "serde_spanned 1.0.2", + "toml_datetime 0.7.1", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ + "indexmap", "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", - "winnow", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.13", +] + +[[package]] +name = "toml_edit" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" +dependencies = [ + "indexmap", + "toml_datetime 0.7.1", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow 0.7.13", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + [[package]] name = "tower" version = "0.5.2" @@ -2443,6 +2996,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2467,9 +3038,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -2480,6 +3051,14 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tx-nonce" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "structs", +] + [[package]] name = "typenum" version = "1.18.0" @@ -2488,15 +3067,15 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -2509,9 +3088,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unwrap-infallible" @@ -2521,21 +3100,16 @@ checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -2548,22 +3122,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] - [[package]] name = "uuid" -version = "1.16.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -2578,6 +3145,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2589,36 +3166,46 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[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", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" dependencies = [ "bumpalo", "log", @@ -2630,9 +3217,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" dependencies = [ "cfg-if", "js-sys", @@ -2643,9 +3230,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2653,9 +3240,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", @@ -2666,63 +3253,63 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" -version = "0.228.0" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d30290541f2d4242a162bbda76b8f2d8b1ac59eab3568ed6f2327d52c9b2c4" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" dependencies = [ "leb128fmt", - "wasmparser 0.228.0", + "wasmparser 0.239.0", ] [[package]] name = "wasmparser" -version = "0.227.1" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" dependencies = [ "bitflags", - "hashbrown", "indexmap", "semver", - "serde", ] [[package]] name = "wasmparser" -version = "0.228.0" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" +checksum = "46d90019b1afd4b808c263e428de644f3003691f243387d30d673211ee0cb8e8" dependencies = [ "bitflags", + "hashbrown", "indexmap", "semver", + "serde", ] [[package]] name = "wasmprinter" -version = "0.227.1" +version = "0.241.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32475a0459db5639e989206dd8833fb07110ec092a7cb3468c82341989cac4d3" +checksum = "68832d23d180e4b8774103c2992b48a1b4d1b62474fd5807efa2e38c7914c4e2" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.227.1", + "wasmparser 0.241.2", ] [[package]] name = "wast" -version = "228.0.0" +version = "239.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5aae124478cb51439f6587f074a3a5e835afd22751c59a87b2e2a882727c97" +checksum = "9139176fe8a2590e0fb174cdcaf373b224cb93c3dde08e4297c1361d2ba1ea5d" dependencies = [ "bumpalo", "leb128fmt", @@ -2733,18 +3320,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.228.0" +version = "1.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec29c89a8d055df988de7236483bf569988ac3d6905899f6af5ef920f9385ad" +checksum = "3e1c941927d34709f255558166f8901a2005f8ab4a9650432e9281b7cc6f3b75" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" dependencies = [ "js-sys", "wasm-bindgen", @@ -2752,64 +3339,50 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.0", ] [[package]] name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-registry" -version = "0.4.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.53.0", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows-result" -version = "0.3.2" +name = "windows-link" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] -name = "windows-strings" -version = "0.3.1" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-link", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.3", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.2.0", ] [[package]] @@ -2830,10 +3403,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -2942,39 +3516,39 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "winnow" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ - "bitflags", + "memchr", ] [[package]] -name = "write16" -version = "1.0.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2984,9 +3558,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -2996,18 +3570,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -3041,11 +3615,22 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -3054,9 +3639,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f14db43ea..8964126aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,14 @@ members = [ "chain-config/meta", "chain-factory", "chain-factory/meta", - "fee-market", - "fee-market/meta", + "mvx-fee-market", + "mvx-fee-market/meta", + "sov-fee-market", + "sov-fee-market/meta", "header-verifier", "header-verifier/meta", + "sovereign-forge", + "sovereign-forge/meta", "sov-esdt-safe", "sov-esdt-safe/meta", "mvx-esdt-safe", @@ -17,5 +21,4 @@ members = [ "testing-sc", "testing-sc/meta", "interactor", - "common/token-whitelist" ] diff --git a/README.md b/README.md index 96456d87e..c028d7cb6 100644 --- a/README.md +++ b/README.md @@ -1 +1,65 @@ -Sovereign Bridge SCs +# Sovereign Bridge Smart Contracts + +Contracts that deploy and run a validator-signed bridge between MultiversX and a Sovereign chain. The focus below is on how the on-chain pieces collaborate; shared helper crates sit alongside these contracts but are not described here. + +## Contract Roles + +- `sovereign-forge/`: entry point for sovereign creators; walks through a four-phase deployment to mint a new chain ID and request contract instances from a Chain Factory. +- `chain-factory/`: factory that clones the contract templates for each sovereign (Chain Config, Header Verifier, MultiversX ESDT Safe, Fee Market) and wires ownership to the correct controller. +- `chain-config/`: stores the sovereign’s configuration and validator set (BLS keys). Handles validator registration during the genesis phase and later updates triggered from signed bridge operations. +- `header-verifier/`: keeps the current validator set and verifies BLS-aggregated signatures for bundles of bridge operations (`hashOfHashes`). It gates execution by marking operation hashes as registered/locked/executed. +- `mvx-esdt-safe/`: MultiversX-side vault. Accepts deposits destined for the sovereign chain (burns or escrows tokens based on the chosen mechanism), emits bridge events, and executes incoming signed operations to mint/unlock tokens or perform contract calls. +- `mvx-fee-market/`: manages bridge fees and whitelists on the MultiversX side. Fee changes and whitelist updates are themselves bridge operations that must be signed and registered by the Header Verifier. +- `sov-esdt-safe/`: sovereign-side vault. Burns incoming tokens, emits the events that validators sign, and exposes admin endpoints for updating its configuration and fee sink address. +- `sov-fee-market/`: sovereign-side fee configuration; owners can set/remove fees, distribute balances, and maintain a fee whitelist. +- `interactor/`: chain-simulator flows for end-to-end tests; see `interactor/HowToRun.md`. +- `testing-sc/`: scenario test contract and fixtures. + +## How the Pieces Interact + +- **Bootstrapping a sovereign:** A creator calls `sovereign-forge` in four phases. The forge asks a `chain-factory` (per shard) to deploy Chain Config, MultiversX ESDT Safe, Fee Market, then Header Verifier. When everything is live, `chain-factory::completeSetupPhase` finalizes the Chain Config setup, then transfers ownership of the MultiversX-side contracts to the Header Verifier so all bridge operations are signature-gated. +- **Validator set lifecycle:** During the genesis phase, validators register BLS keys in `chain-config`. The Header Verifier pulls this set on `completeSetupPhase`. Future rotations happen through `header-verifier::changeValidatorSet`, which requires a signed operation hash from the previous epoch and the list of new validator key IDs stored in Chain Config. +- **Sovereign → MultiversX transfers:** Users deposit into `sov-esdt-safe`, which burns the tokens and emits a deposit event. Sovereign validators batch those events into a list of operations, sign the resulting `hashOfHashes`, and the Sovereign Bridge Service calls `header-verifier::registerBridgeOps` on MultiversX. Each operation is executed through `mvx-esdt-safe::executeBridgeOps`, which locks the operation hash in the Header Verifier, mints/unlocks the needed tokens (or performs a contract call), and then signals completion so the hash is cleared. +- **MultiversX → Sovereign transfers:** Users call `mvx-esdt-safe::deposit`. The contract enforces whitelists/blacklists and fee collection, then either burns wrapped tokens or escrows native ones before emitting a deposit event. Sovereign validators observe these events and mint/unlock the corresponding assets on the sovereign chain according to their local logic. +- **Token mechanics:** `mvx-esdt-safe` supports two modes per token: burn (requires local mint/burn roles and the token to be trusted) or lock (escrow on MultiversX, unlock on return). Registering new sovereign-minted tokens on MultiversX (`registerToken`) issues a new ESDT with the sovereign prefix and maps it to the sovereign identifier; `registerNativeToken` bootstraps the sovereign chain’s own native asset. +- **Fee handling:** Deposits can require an upfront fee payment that is forwarded to `mvx-fee-market::subtractFee`. The MultiversX fee market also exposes bridge-controlled operations to set/remove fees, distribute balances, and manage a whitelist; these paths are guarded by the Header Verifier just like token transfers. +- **Pause and safeguards:** Both safes can be paused; setup phases must be completed before normal bridge operations proceed; hash locking in the Header Verifier prevents duplicate execution and enforces operation nonces. + +## System Diagram + +``` +Sovereign Creator + | + v deploy phases +sovereign-forge -> chain-factory ----------------------+ + | | | + | +--> chain-config (validators) | + | +--> mvx-esdt-safe (vault) | + | +--> mvx-fee-market (fees) | + | +--> header-verifier (owner) <--+ + | +Sovereign Chain MultiversX +------------------ ------------------- + sov-esdt-safe (burn & emit) ----> Validators sign ----> header-verifier + | | + sov-fee-market (fees) <---- fee lookups ----- mvx-esdt-safe -- mvx-fee-market + | + executeBridgeOps <-----+ + (mint/unlock/SC calls) +``` + +> For more details about the Cross-Chain Execution, please take a look at the [official documentation](https://docs.multiversx.com/sovereign/cross-chain-execution). + +## Development + +- Build all contracts: `sc-meta all build` +- Run contract tests from the repo root or within a contract crate: `sc-meta test` +- Simulator E2E flows: follow `interactor/HowToRun.md` (start `sc-meta cs`, delete stale `state.toml`, run the `always_deploy_setup_first` test to seed state, then execute specific tests). + +## Repository Map + +- `sovereign-forge/`, `chain-factory/`: deployment orchestration +- `chain-config/`, `header-verifier/`: validator management and signature verification +- `mvx-esdt-safe/`, `mvx-fee-market/`: MultiversX bridge vault and fee logic +- `sov-esdt-safe/`, `sov-fee-market/`: sovereign-side vault and fee logic +- `interactor/`, `testing-sc/`: integration and scenario tests diff --git a/chain-config/Cargo.toml b/chain-config/Cargo.toml index e9eb94ec9..0d65d0cbd 100644 --- a/chain-config/Cargo.toml +++ b/chain-config/Cargo.toml @@ -12,13 +12,35 @@ path = "src/lib.rs" num-bigint = "0.4.2" [dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" +version = "0.63.0" +features = ["bls"] + +[dev-dependencies.common-test-setup] +path = "../common/common-test-setup" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dependencies.multiversx-sc-modules] -version = "=0.57.1" +version = "0.63.0" + +[dependencies.structs] +path = "../common/structs" + +[dependencies.proxies] +path = "../common/proxies" + +[dependencies.setup-phase] +path = "../common/setup-phase" [dependencies.error-messages] path = "../common/error-messages" + +[dependencies.cross-chain] +path = "../common/cross-chain" + +[dependencies.custom-events] +path = "../common/custom-events" + +[dependencies.common-utils] +path = "../common/common-utils" diff --git a/chain-config/README.md b/chain-config/README.md new file mode 100644 index 000000000..d96ccc6b0 --- /dev/null +++ b/chain-config/README.md @@ -0,0 +1,9 @@ +# Chain Config Contract + +Maintains the validator set and sovereign chain configuration. Genesis registration happens here before the bridge is opened, and later updates come through signed bridge operations. + +- **Initialization:** `init` accepts an optional `SovereignConfig` (min/max validators, stakes, limits). Defaults are applied if none is provided. +- **Validator lifecycle:** Validators register/unregister during the setup phase via `register` / `unregister`. After setup, updates flow through bridge-controlled endpoints `registerBlsKey` and `unregisterBlsKey` (operation hashes are locked through the Header Verifier). +- **Configuration updates:** Owners can adjust the sovereign config during setup with `updateSovereignConfigSetupPhase`; after setup the signed path `updateSovereignConfig` is used. +- **Completing setup:** `completeSetupPhase` finalizes genesis once the minimum validator count is present. The Header Verifier then mirrors the validator set for signature checks. +- **Interactions:** The Header Verifier reads the BLS key map stored here to verify bridge operations. Sovereign Forge deploys this contract first, and Chain Factory clones it from a template when spinning up a new sovereign. diff --git a/chain-config/meta/Cargo.toml b/chain-config/meta/Cargo.toml index 5e4aa407a..8b1666e8a 100644 --- a/chain-config/meta/Cargo.toml +++ b/chain-config/meta/Cargo.toml @@ -11,4 +11,4 @@ authors = ["you"] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "=0.57.1" +version = "0.63.0" diff --git a/chain-config/sc-config.toml b/chain-config/sc-config.toml new file mode 100644 index 000000000..4ef3df809 --- /dev/null +++ b/chain-config/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "../common/proxies/src/chain_config_proxy.rs" diff --git a/chain-config/src/bridge.rs b/chain-config/src/bridge.rs deleted file mode 100644 index f06731284..000000000 --- a/chain-config/src/bridge.rs +++ /dev/null @@ -1,42 +0,0 @@ -use error_messages::BRIDGE_ALREADY_DEPLOYED; - -multiversx_sc::imports!(); - -mod bridge_proxy { - multiversx_sc::imports!(); - - #[multiversx_sc::proxy] - pub trait BridgeProxy { - #[init] - fn init(&self, min_valid_signers: u32, signers: MultiValueEncoded); - } -} - -#[multiversx_sc::module] -pub trait BridgeModule { - #[only_owner] - #[endpoint(deployBridge)] - fn deploy_bridge( - &self, - code: ManagedBuffer, - min_valid_signers: u32, - signers: MultiValueEncoded, - ) { - require!(self.bridge_address().is_empty(), BRIDGE_ALREADY_DEPLOYED); - - let metadata = - CodeMetadata::PAYABLE_BY_SC | CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE; - let (sc_address, _) = self - .bridge_proxy() - .init(min_valid_signers, signers) - .deploy_contract::(&code, metadata); - - self.bridge_address().set(sc_address); - } - - #[proxy] - fn bridge_proxy(&self) -> bridge_proxy::Proxy; - - #[storage_mapper("bridgeAddress")] - fn bridge_address(&self) -> SingleValueMapper; -} diff --git a/chain-config/src/config_utils.rs b/chain-config/src/config_utils.rs new file mode 100644 index 000000000..a74aba4f6 --- /dev/null +++ b/chain-config/src/config_utils.rs @@ -0,0 +1,133 @@ +use error_messages::{ + ADDITIONAL_STAKE_NOT_REQUIRED, ADDITIONAL_STAKE_ZERO_VALUE, + DUPLICATE_ADDITIONAL_STAKE_TOKEN_ID, INVALID_ADDITIONAL_STAKE, INVALID_BLS_KEY_FOR_CALLER, + INVALID_EGLD_STAKE, INVALID_MIN_MAX_VALIDATOR_NUMBERS, INVALID_TOKEN_ID, +}; +use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; +use structs::{configs::SovereignConfig, ValidatorInfo}; + +use crate::storage; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait ChainConfigUtilsModule: storage::ChainConfigStorageModule { + // What should be the maximum number of validators ? + fn is_new_config_valid(&self, config: &SovereignConfig) -> Option<&str> { + if let Some(additional_stake) = config.opt_additional_stake_required.clone() { + let mut seen_token_ids = ManagedMap::new(); + + for stake in additional_stake { + let token_id = stake.token_identifier.clone(); + if !token_id.is_valid_esdt_identifier() { + return Some(INVALID_TOKEN_ID); + } + if seen_token_ids.contains(token_id.as_managed_buffer()) { + return Some(DUPLICATE_ADDITIONAL_STAKE_TOKEN_ID); + } + if stake.amount <= 0 { + return Some(ADDITIONAL_STAKE_ZERO_VALUE); + } + + seen_token_ids.put(token_id.as_managed_buffer(), &ManagedBuffer::new()); + } + } + + if config.min_validators <= config.max_validators { + None + } else { + Some(INVALID_MIN_MAX_VALIDATOR_NUMBERS) + } + } + + fn refund_stake( + &self, + caller: &ManagedAddress, + validator_info: &ValidatorInfo, + ) { + let stake = self.get_total_stake(validator_info); + if stake.is_empty() { + return; + } + + self.tx().to(caller).payment(stake).transfer_execute(); + } + + fn get_total_stake( + &self, + validator_info: &ValidatorInfo, + ) -> MultiEgldOrEsdtPayment { + let mut total_stake = MultiEgldOrEsdtPayment::new(); + if validator_info.egld_stake > 0 { + total_stake.push(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(ManagedBuffer::from(EGLD_000000_TOKEN_IDENTIFIER)), + 0, + validator_info.egld_stake.clone(), + )); + } + + if let Some(additional_stake) = &validator_info.token_stake { + for stake in additional_stake { + total_stake.push(stake.clone().into()); + } + } + + total_stake + } + + fn validate_stake( + &self, + ) -> ( + BigUint, + Option>>, + ) { + let sovereign_config = self.sovereign_config().get(); + + let (egld_amount, esdt_payments) = self.split_payments(); + + require!( + egld_amount == sovereign_config.min_stake, + INVALID_EGLD_STAKE + ); + + if let Some(additional) = &sovereign_config.opt_additional_stake_required { + let valid = additional.iter().all(|s| { + esdt_payments + .iter() + .any(|p| p.token_identifier == s.token_identifier && p.amount == s.amount) + }); + require!(valid, INVALID_ADDITIONAL_STAKE); + } else { + require!(esdt_payments.is_empty(), ADDITIONAL_STAKE_NOT_REQUIRED); + } + + (egld_amount, Some(esdt_payments)) + } + + fn split_payments(&self) -> (BigUint, ManagedVec>) { + let mut egld_amount = BigUint::zero(); + let mut esdt_payments = ManagedVec::new(); + + for payment in self.call_value().all_transfers().clone_value().into_iter() { + if payment.token_identifier.is_egld() { + egld_amount += payment.amount.clone(); + } else { + esdt_payments.push(payment.unwrap_esdt()); + } + } + + (egld_amount, esdt_payments) + } + + fn require_caller_has_bls_key( + &self, + caller: &ManagedAddress, + validator_info: &ValidatorInfo, + ) { + require!( + validator_info.address == *caller, + INVALID_BLS_KEY_FOR_CALLER + ); + } +} diff --git a/chain-config/src/configs.rs b/chain-config/src/configs.rs new file mode 100644 index 000000000..f2ef21995 --- /dev/null +++ b/chain-config/src/configs.rs @@ -0,0 +1,69 @@ +use error_messages::{SETUP_PHASE_ALREADY_COMPLETED, SETUP_PHASE_NOT_COMPLETED}; +use structs::{ + configs::{SovereignConfig, UpdateSovereignConfigOperation}, + generate_hash::GenerateHash, +}; + +use crate::{config_utils, storage, validator}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ConfigsModule: + validator::ValidatorModule + + storage::ChainConfigStorageModule + + common_utils::CommonUtilsModule + + config_utils::ChainConfigUtilsModule + + custom_events::CustomEventsModule + + setup_phase::SetupPhaseModule +{ + #[only_owner] + #[endpoint(updateSovereignConfigSetupPhase)] + fn update_sovereign_config_during_setup_phase(&self, new_config: SovereignConfig) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + if let Some(error_message) = self.is_new_config_valid(&new_config) { + sc_panic!(error_message); + } + self.sovereign_config().set(new_config); + } + + #[endpoint(updateSovereignConfig)] + fn update_sovereign_config( + &self, + hash_of_hashes: ManagedBuffer, + update_config_operation: UpdateSovereignConfigOperation, + ) { + let config_hash = update_config_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &config_hash, + update_config_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &config_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &config_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + + if let Some(error_message) = + self.is_new_config_valid(&update_config_operation.sovereign_config) + { + self.complete_operation(&hash_of_hashes, &config_hash, Some(error_message.into())); + return; + } else { + self.sovereign_config() + .set(update_config_operation.sovereign_config); + self.complete_operation(&hash_of_hashes, &config_hash, None); + } + } +} diff --git a/chain-config/src/lib.rs b/chain-config/src/lib.rs index fccdd50d7..849bb9f88 100644 --- a/chain-config/src/lib.rs +++ b/chain-config/src/lib.rs @@ -1,50 +1,50 @@ #![no_std] -use error_messages::INVALID_MIN_MAX_VALIDATOR_NUMBERS; -use validator_rules::TokenIdAmountPair; +use multiversx_sc::imports::*; +use structs::configs::SovereignConfig; multiversx_sc::imports!(); -pub mod bridge; -pub mod validator_rules; - -pub type StakeMultiArg = MultiValue2, BigUint>; +pub mod config_utils; +pub mod configs; +pub mod storage; +pub mod validator; #[multiversx_sc::contract] pub trait ChainConfigContract: - bridge::BridgeModule - + validator_rules::ValidatorRulesModule - + multiversx_sc_modules::only_admin::OnlyAdminModule + validator::ValidatorModule + + storage::ChainConfigStorageModule + + config_utils::ChainConfigUtilsModule + + configs::ConfigsModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule { #[init] - fn init( - &self, - min_validators: usize, - max_validators: usize, - min_stake: BigUint, - admin: ManagedAddress, - additional_stake_required: MultiValueEncoded>, - ) { - require!( - min_validators <= max_validators, - INVALID_MIN_MAX_VALIDATOR_NUMBERS - ); - - let mut additional_stake_vec = ManagedVec::new(); - for multi_value in additional_stake_required { - let (token_id, amount) = multi_value.into_tuple(); - let value = TokenIdAmountPair { token_id, amount }; - - additional_stake_vec.push(value); - } - - self.min_validators().set(min_validators); - self.max_validators().set(max_validators); - self.min_stake().set(min_stake); - self.add_admin(admin); - self.additional_stake_required().set(additional_stake_vec); + fn init(&self, opt_config: OptionalValue>) { + let new_config = match opt_config { + OptionalValue::Some(cfg) => { + if let Some(error_message) = self.is_new_config_valid(&cfg) { + sc_panic!(error_message); + } + cfg + } + OptionalValue::None => SovereignConfig::default_config(), + }; + + self.sovereign_config().set(new_config.clone()); } #[upgrade] fn upgrade(&self) {} + + #[only_owner] + #[endpoint(completeSetupPhase)] + fn complete_setup_phase(&self) { + if self.is_setup_phase_complete() { + return; + } + self.require_validator_set_valid(self.bls_keys_map().len()); + self.setup_phase_complete().set(true); + } } diff --git a/chain-config/src/storage.rs b/chain-config/src/storage.rs new file mode 100644 index 000000000..396e95ab3 --- /dev/null +++ b/chain-config/src/storage.rs @@ -0,0 +1,76 @@ +use error_messages::{ + INVALID_BLS_KEY_PROVIDED, NOT_ENOUGH_VALIDATORS, VALIDATOR_ALREADY_REGISTERED, + VALIDATOR_NOT_REGISTERED, VALIDATOR_RANGE_EXCEEDED, +}; +use structs::{configs::SovereignConfig, ValidatorInfo, BLS_KEY_BYTE_LENGTH}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ChainConfigStorageModule { + fn require_valid_bls_key(&self, bls_key: &ManagedBuffer) { + require!( + bls_key.len() == BLS_KEY_BYTE_LENGTH, + INVALID_BLS_KEY_PROVIDED + ); + } + + fn require_validator_not_registered(&self, bls_key: &ManagedBuffer) { + require!( + self.bls_key_to_id_mapper(bls_key).is_empty(), + VALIDATOR_ALREADY_REGISTERED + ); + } + + fn require_valid_validator_range( + &self, + current_bls_key_id: &BigUint, + max_number_of_validators: u64, + ) { + require!( + *current_bls_key_id <= max_number_of_validators, + VALIDATOR_RANGE_EXCEEDED + ); + } + + fn require_validator_registered(&self, bls_key: &ManagedBuffer) { + require!( + !self.bls_key_to_id_mapper(bls_key).is_empty(), + VALIDATOR_NOT_REGISTERED + ); + } + + fn require_validator_set_valid(&self, validator_len: usize) { + let config = self.sovereign_config().get(); + + require!( + validator_len as u64 >= config.min_validators, + NOT_ENOUGH_VALIDATORS + ); + } + + #[view(sovereignConfig)] + #[storage_mapper("sovereignConfig")] + fn sovereign_config(&self) -> SingleValueMapper>; + + #[view(blsKeyToId)] + #[storage_mapper("blsKeyToId")] + fn bls_key_to_id_mapper( + &self, + bls_key: &ManagedBuffer, + ) -> SingleValueMapper>; + + #[view(validator_info)] + #[storage_mapper("validator_info")] + fn validator_info( + &self, + id: &BigUint, + ) -> SingleValueMapper>; + + #[view(blsKeysMap)] + #[storage_mapper("blsKeysMap")] + fn bls_keys_map(&self) -> MapMapper, ManagedBuffer>; + + #[storage_mapper("lastBlsKeyId")] + fn last_bls_key_id(&self) -> SingleValueMapper>; +} diff --git a/chain-config/src/validator.rs b/chain-config/src/validator.rs new file mode 100644 index 000000000..d731cb842 --- /dev/null +++ b/chain-config/src/validator.rs @@ -0,0 +1,165 @@ +use crate::{config_utils, storage}; +use error_messages::{ + INVALID_VALIDATOR_DATA, REGISTRATIONS_DISABLED_GENESIS_PHASE, VALIDATOR_ID_NOT_REGISTERED, +}; +use structs::generate_hash::GenerateHash; +use structs::{ValidatorInfo, ValidatorOperation}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait ValidatorModule: + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + storage::ChainConfigStorageModule + + config_utils::ChainConfigUtilsModule +{ + #[payable] + #[endpoint(register)] + fn register(&self, bls_key: ManagedBuffer) { + if self.is_setup_phase_complete() { + sc_panic!(REGISTRATIONS_DISABLED_GENESIS_PHASE); + } + + self.require_valid_bls_key(&bls_key); + self.require_validator_not_registered(&bls_key); + + let current_bls_key_id = self.get_bls_key_id(); + + let (egld_stake, additional_stake) = self.validate_stake(); + + self.insert_validator( + current_bls_key_id.clone(), + &ValidatorInfo { + address: self.blockchain().get_caller(), + bls_key, + egld_stake, + token_stake: additional_stake, + }, + ); + } + + #[inline] + fn get_bls_key_id(&self) -> BigUint { + let max_number_of_validators = self.sovereign_config().get().max_validators; + let last_bls_key_id_mapper = self.last_bls_key_id(); + let current_bls_key_id = &last_bls_key_id_mapper.get() + 1u32; + self.last_bls_key_id().set(current_bls_key_id.clone()); + + self.require_valid_validator_range(¤t_bls_key_id, max_number_of_validators); + + current_bls_key_id + } + + #[endpoint(registerBlsKey)] + fn register_bls_key( + &self, + hash_of_hashes: ManagedBuffer, + validator_operation: ValidatorOperation, + ) { + let config_hash = validator_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &config_hash, + validator_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &config_hash, Some(lock_operation_error)); + return; + } + + self.insert_validator( + validator_operation.validator_data.id, + &ValidatorInfo { + address: validator_operation.validator_data.address, + bls_key: validator_operation.validator_data.bls_key, + egld_stake: BigUint::zero(), + token_stake: None, + }, + ); + + self.complete_operation(&hash_of_hashes, &config_hash, None); + } + + #[inline] + fn insert_validator(&self, id: BigUint, validator_info: &ValidatorInfo) { + self.bls_keys_map() + .insert(id.clone(), validator_info.bls_key.clone()); + self.bls_key_to_id_mapper(&validator_info.bls_key) + .set(id.clone()); + self.validator_info(&id).set(validator_info); + } + + #[endpoint(unregister)] + fn unregister(&self, bls_key: ManagedBuffer) { + if self.is_setup_phase_complete() { + sc_panic!(REGISTRATIONS_DISABLED_GENESIS_PHASE); + } + + self.require_valid_bls_key(&bls_key); + self.require_validator_registered(&bls_key); + + let caller = self.blockchain().get_caller(); + let validator_id = self.bls_key_to_id_mapper(&bls_key).get(); + let validator_info = self.validator_info(&validator_id).get(); + + self.require_caller_has_bls_key(&caller, &validator_info); + + self.remove_validator(validator_id, &validator_info); + + self.refund_stake(&caller, &validator_info); + } + + #[endpoint(unregisterBlsKey)] + fn unregister_bls_key( + &self, + hash_of_hashes: ManagedBuffer, + validator_operation: ValidatorOperation, + ) { + let config_hash = validator_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &config_hash, + validator_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &config_hash, Some(lock_operation_error)); + return; + } + + let validator_info_mapper = self.validator_info(&validator_operation.validator_data.id); + if validator_info_mapper.is_empty() { + self.complete_operation( + &hash_of_hashes, + &config_hash, + Some(VALIDATOR_ID_NOT_REGISTERED.into()), + ); + return; + } + let validator_info = self + .validator_info(&validator_operation.validator_data.id) + .get(); + + if validator_operation.validator_data.address != validator_info.address { + self.complete_operation( + &hash_of_hashes, + &config_hash, + Some(INVALID_VALIDATOR_DATA.into()), + ); + return; + } + + self.remove_validator(validator_operation.validator_data.id, &validator_info); + + self.refund_stake(&validator_operation.validator_data.address, &validator_info); + + self.complete_operation(&hash_of_hashes, &config_hash, None); + } + + #[inline] + fn remove_validator(&self, id: BigUint, validator_info: &ValidatorInfo) { + self.bls_keys_map().remove(&id); + self.bls_key_to_id_mapper(&validator_info.bls_key).clear(); + self.validator_info(&id).clear(); + } +} diff --git a/chain-config/src/validator_rules.rs b/chain-config/src/validator_rules.rs deleted file mode 100644 index 971f65e2d..000000000 --- a/chain-config/src/validator_rules.rs +++ /dev/null @@ -1,39 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -// TODO: What to fill here? -pub enum SlashableOffenses {} - -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem)] -pub struct TokenIdAmountPair { - pub token_id: TokenIdentifier, - pub amount: BigUint, -} - -#[multiversx_sc::module] -pub trait ValidatorRulesModule { - #[view(getMinValidators)] - #[storage_mapper("minValidators")] - fn min_validators(&self) -> SingleValueMapper; - - #[view(getMaxValidators)] - #[storage_mapper("maxValidators")] - fn max_validators(&self) -> SingleValueMapper; - - // TODO: Read user stake and verify - #[view(getMinStake)] - #[storage_mapper("minStake")] - fn min_stake(&self) -> SingleValueMapper; - - // TODO: Read user stake and verify - #[view(getAdditionalStakeRequired)] - #[storage_mapper("additionalStakeRequired")] - fn additional_stake_required( - &self, - ) -> SingleValueMapper>>; - - #[view(wasPreviouslySlashed)] - #[storage_mapper("wasPreviouslySlashed")] - fn was_previously_slashed(&self, validator: &ManagedAddress) -> SingleValueMapper; -} diff --git a/chain-config/tests/chain_config_blackbox_setup.rs b/chain-config/tests/chain_config_blackbox_setup.rs new file mode 100644 index 000000000..3122589f0 --- /dev/null +++ b/chain-config/tests/chain_config_blackbox_setup.rs @@ -0,0 +1,147 @@ +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + base_setup::log_validations::assert_expected_logs, + constants::{ + CHAIN_CONFIG_ADDRESS, EXECUTED_BRIDGE_OP_EVENT, FIRST_TEST_TOKEN, ONE_HUNDRED_MILLION, + OWNER_ADDRESS, OWNER_BALANCE, UPDATE_SOVEREIGN_CONFIG_ENDPOINT, USER_ADDRESS, + }, + log, +}; +use multiversx_sc::types::{ + BigUint, ManagedBuffer, ReturnsHandledOrError, ReturnsResult, TestAddress, +}; +use multiversx_sc_scenario::imports::OptionalValue; +use multiversx_sc_scenario::{api::StaticApi, ReturnsLogs, ScenarioTxRun}; +use proxies::chain_config_proxy::ChainConfigContractProxy; +use structs::{ + aliases::TxNonce, + configs::{SovereignConfig, UpdateSovereignConfigOperation}, +}; + +pub struct ChainConfigTestState { + pub common_setup: BaseSetup, +} + +impl ChainConfigTestState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let owner_account = AccountSetup { + address: OWNER_ADDRESS.to_address(), + code_path: None, + egld_balance: Some(OWNER_BALANCE.into()), + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into())]), + }; + + let user_account = AccountSetup { + address: USER_ADDRESS.to_address(), + code_path: None, + egld_balance: Some(OWNER_BALANCE.into()), + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into())]), + }; + + let account_setups = vec![owner_account, user_account]; + + let common_setup = BaseSetup::new(account_setups); + + Self { common_setup } + } + + pub fn update_sovereign_config_during_setup_phase( + &mut self, + config: SovereignConfig, + expected_error_message: Option<&str>, + ) { + let result = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .update_sovereign_config_during_setup_phase(config) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(result, expected_error_message); + } + + pub fn update_sovereign_config( + &mut self, + hash_of_hashes: ManagedBuffer, + config: SovereignConfig, + operation_nonce: TxNonce, + expected_error_message: Option<&str>, + ) { + let (result, logs) = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .update_sovereign_config( + hash_of_hashes, + UpdateSovereignConfigOperation { + sovereign_config: config, + nonce: operation_nonce, + }, + ) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.common_setup + .assert_expected_error_message(result, None); + + let expected_logs = vec![ + log!(UPDATE_SOVEREIGN_CONFIG_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_error_message), + ]; + + assert_expected_logs(logs, expected_logs); + } + + pub fn unregister_with_caller( + &mut self, + bls_key: &ManagedBuffer, + caller: TestAddress, + expect_error: Option<&str>, + ) { + let result = self + .common_setup + .world + .tx() + .from(caller) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .unregister(bls_key) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(result, expect_error); + } + + pub fn get_bls_key_by_id(&mut self, id: &BigUint) -> ManagedBuffer { + let (_, bls_key) = self + .common_setup + .world + .query() + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .bls_keys_map() + .returns(ReturnsResult) + .run() + .into_iter() + .find(|v| { + let (returned_id, _) = v.clone().into_tuple(); + + returned_id.eq(id) + }) + .unwrap() + .into_tuple(); + + bls_key + } +} diff --git a/chain-config/tests/chain_config_blackbox_tests.rs b/chain-config/tests/chain_config_blackbox_tests.rs new file mode 100644 index 000000000..d32cd81e7 --- /dev/null +++ b/chain-config/tests/chain_config_blackbox_tests.rs @@ -0,0 +1,1010 @@ +use chain_config::storage::ChainConfigStorageModule; +use chain_config_blackbox_setup::ChainConfigTestState; +use common_test_setup::base_setup::helpers::BLSKey; +use common_test_setup::constants::{ + CHAIN_CONFIG_ADDRESS, FIRST_TEST_TOKEN, ONE_HUNDRED_MILLION, ONE_HUNDRED_THOUSAND, + OWNER_ADDRESS, OWNER_BALANCE, USER_ADDRESS, +}; +use error_messages::{ + ADDITIONAL_STAKE_ZERO_VALUE, CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE, INVALID_ADDITIONAL_STAKE, + INVALID_BLS_KEY_FOR_CALLER, INVALID_BLS_KEY_PROVIDED, INVALID_EGLD_STAKE, + INVALID_MIN_MAX_VALIDATOR_NUMBERS, INVALID_VALIDATOR_DATA, + REGISTRATIONS_DISABLED_GENESIS_PHASE, SETUP_PHASE_NOT_COMPLETED, VALIDATOR_ALREADY_REGISTERED, + VALIDATOR_ID_NOT_REGISTERED, VALIDATOR_NOT_REGISTERED, VALIDATOR_RANGE_EXCEEDED, +}; +use multiversx_sc::{ + chain_core::EGLD_000000_TOKEN_IDENTIFIER, + imports::OptionalValue, + types::{ + BigUint, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, ManagedBuffer, ManagedVec, + MultiEgldOrEsdtPayment, MultiValueEncoded, + }, +}; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_scenario::ScenarioTxWhitebox; +use setup_phase::SetupPhaseModule; +use structs::{ + configs::{SovereignConfig, StakeArgs}, + forge::ScArray, + generate_hash::GenerateHash, + ValidatorData, +}; + +mod chain_config_blackbox_setup; + +/// ### TEST +/// C-CONFIG_DEPLOY_OK +/// +/// ### ACTION +/// Deploy chain-config with default config +/// +/// ### EXPECTED +/// Chain config is deployed +#[test] +fn test_deploy_chain_config_default_config() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); +} + +/// ### TEST +/// C-CONFIG_DEPLOY_OK +/// +/// ### ACTION +/// Deploy chain-config with specific config +/// +/// ### EXPECTED +/// Chain config is deployed +#[test] +fn test_deploy_chain_config() { + let mut state = ChainConfigTestState::new(); + + let config = SovereignConfig::new(1, 2, BigUint::from(100u32), None); + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); +} + +/// ### TEST +/// C-CONFIG_DEPLOY_FAIL +/// +/// ### ACTION +/// Call 'update_chain_config_during_setup_phase()' with a invalid config +/// +/// ### EXPECTED +/// ERROR INVALID_MIN_MAX_VALIDATOR_NUMBERS +#[test] +fn test_deploy_chain_config_invalid_config() { + let mut state = ChainConfigTestState::new(); + + let config = SovereignConfig { + min_validators: 2, + max_validators: 1, + ..SovereignConfig::default_config_for_test() + }; + + state.common_setup.deploy_chain_config( + OptionalValue::Some(config), + Some(INVALID_MIN_MAX_VALIDATOR_NUMBERS), + ); +} + +/// ### TEST +/// C-CONFIG_COMPLETE_SETUP_PHASE_OK +/// +/// ### ACTION +/// Call `complete_setup_phase()` +/// +/// ### EXPECTED +/// Setup phase is completed +#[test] +fn test_complete_setup_phase() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .world + .query() + .to(CHAIN_CONFIG_ADDRESS) + .whitebox(chain_config::contract_obj, |sc| { + assert!(sc.is_setup_phase_complete()); + }) +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_DURING_SETUP_PHASE_OK +/// +/// ### ACTION +/// Call 'update_chain_config_during_setup_phase()' with a new valid config +/// +/// ### EXPECTED +/// Chain config is updated with the new config +#[test] +fn test_update_config_during_setup_phase() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let new_config = SovereignConfig::new(2, 4, BigUint::default(), None); + + state.update_sovereign_config_during_setup_phase(new_config, None); +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_DURING_SETUP_PHASE_FAIL +/// +/// ### ACTION +/// Call 'update_config()' with additional stake with a zero amount +/// +/// ### EXPECTED +/// Error ADDITIONAL_STAKE_ZERO_VALUE +#[test] +fn test_update_config_during_setup_phase_additional_stake_zero_amount() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let first_token_stake_arg = StakeArgs { + token_identifier: FIRST_TEST_TOKEN.to_token_identifier(), + amount: BigUint::zero(), + }; + + let additional_stage_args = ManagedVec::from(vec![first_token_stake_arg]); + + let new_config = SovereignConfig::new(2, 4, BigUint::default(), Some(additional_stage_args)); + + state.update_sovereign_config_during_setup_phase(new_config, Some(ADDITIONAL_STAKE_ZERO_VALUE)); +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_DURING_SETUP_PHASE_FAIL +/// +/// ### ACTION +/// Call 'update_chain_config_during_setup_phase()' with an new invalid config +/// +/// ### EXPECTED +/// Error INVALID_MIN_MAX_VALIDATOR_NUMBERS +#[test] +fn test_update_config_during_setup_phase_wrong_validators_array() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let new_config = SovereignConfig::new(2, 1, BigUint::default(), None); + + state.update_sovereign_config_during_setup_phase( + new_config, + Some(INVALID_MIN_MAX_VALIDATOR_NUMBERS), + ); +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_FAIL +/// +/// ### ACTION +/// Call 'update_sovereign_config()' during the setup phase +/// +/// ### EXPECTED +/// Error SETUP_PHASE_NOT_COMPLETED +#[test] +fn test_update_config_setup_phase_not_completed() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(Some(CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE)); + + let new_config = SovereignConfig::new(2, 1, BigUint::default(), None); + + let operation_nonce = state.common_setup.next_operation_nonce(); + + state.update_sovereign_config( + ManagedBuffer::new(), + new_config, + operation_nonce, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_OK +/// +/// ### ACTION +/// Call 'update_sovereign_config()' with an invalid config +/// +/// ### EXPECTED +/// failedBridgeOp event is emitted +#[test] +fn test_update_config_invalid_config() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + let new_config = SovereignConfig::new(2, 1, BigUint::default(), None); + let config_hash = new_config.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&config_hash.to_vec())); + let (signature, pub_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + let bitmap = state.common_setup.full_bitmap(1); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + 0, + MultiValueEncoded::from_iter(vec![config_hash]), + ); + + let operation_nonce = state.common_setup.next_operation_nonce(); + + state.update_sovereign_config( + hash_of_hashes, + new_config, + operation_nonce, + Some(INVALID_MIN_MAX_VALIDATOR_NUMBERS), + ); +} + +/// ### TEST +/// C-CONFIG_UPDATE_CONFIG_OK +/// +/// ### ACTION +/// Call 'update_sovereign_config()' +/// +/// ### EXPECTED +/// executedBridgeOp event is emitted +#[test] +fn test_update_config() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + let new_config = SovereignConfig::new(1, 2, BigUint::default(), None); + let config_hash = new_config.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&config_hash.to_vec())); + let (signature, pub_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + let bitmap = state.common_setup.full_bitmap(1); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + 0, + MultiValueEncoded::from_iter(vec![config_hash]), + ); + + state.common_setup.complete_chain_config_setup_phase(); + + let operation_nonce = state.common_setup.next_operation_nonce(); + + state.update_sovereign_config(hash_of_hashes, new_config, operation_nonce, None); + + state + .common_setup + .world + .query() + .to(CHAIN_CONFIG_ADDRESS) + .whitebox(chain_config::contract_obj, |sc| { + let config = sc.sovereign_config().get(); + assert!(config.min_validators == 1 && config.max_validators == 2); + }); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'register()' with too many validators +/// +/// ### EXPECTED +/// Error VALIDATOR_RANGE_EXCEEDED +#[test] +fn test_register_range_exceeded_too_many_validators() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let payments_vec = MultiEgldOrEsdtPayment::new(); + + let new_validator_one = BLSKey::random(); + let new_validator_two = BLSKey::random(); + let new_validator_three = BLSKey::random(); + + state + .common_setup + .register(&new_validator_one, &payments_vec, None); + let id_one = state.common_setup.get_bls_key_id(&new_validator_one); + assert!(state.get_bls_key_by_id(&id_one) == new_validator_one); + + state + .common_setup + .register(&new_validator_two, &payments_vec, None); + let id_two = state.common_setup.get_bls_key_id(&new_validator_two); + assert!(state.get_bls_key_by_id(&id_two) == new_validator_two); + + state.common_setup.register( + &new_validator_three, + &payments_vec, + Some(VALIDATOR_RANGE_EXCEEDED), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'register()' with not enough EGLD stake +/// +/// ### EXPECTED +/// Error INVALID_EGLD_STAKE +#[test] +fn test_register_not_enough_egld_stake() { + let mut state = ChainConfigTestState::new(); + + let config = SovereignConfig { + max_validators: 3, + min_stake: BigUint::from(100u64), + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); + + let egld_payment_not_enough = EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(EGLD_000000_TOKEN_IDENTIFIER.as_bytes()), + 0, + BigUint::from(99u64), + ); + + let mut payments_vec_not_enough = MultiEgldOrEsdtPayment::new(); + payments_vec_not_enough.push(egld_payment_not_enough); + + state.common_setup.register( + &BLSKey::random(), + &payments_vec_not_enough, + Some(INVALID_EGLD_STAKE), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'register()' with already registered validator +/// +/// ### EXPECTED +/// Error VALIDATOR_ALREADY_REGISTERED +#[test] +fn test_register_already_registered() { + let mut state = ChainConfigTestState::new(); + + let sovereign_config = SovereignConfig { + max_validators: 10, + ..SovereignConfig::default_config_for_test() + }; + state + .common_setup + .deploy_chain_config(OptionalValue::Some(sovereign_config), None); + + let payments_vec = MultiEgldOrEsdtPayment::new(); + + state + .common_setup + .register(&BLSKey::random(), &payments_vec, None); + + let new_validator = BLSKey::random(); + state + .common_setup + .register(&new_validator, &payments_vec, None); + assert!(state.common_setup.get_bls_key_id(&new_validator) == 2); + + state.common_setup.register( + &new_validator, + &payments_vec, + Some(VALIDATOR_ALREADY_REGISTERED), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'register()' with invalid BLS key +/// +/// ### EXPECTED +/// Error INVALID_BLS_KEY_PROVIDED +#[test] +fn test_register_invalid_bls_key() { + let mut state = ChainConfigTestState::new(); + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state.common_setup.register( + &ManagedBuffer::from("invalid bls key"), + &MultiEgldOrEsdtPayment::new(), + Some(INVALID_BLS_KEY_PROVIDED), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'register()' after genesis phase +/// +/// ### EXPECTED +/// Error REGISTRATIONS_DISABLED_GENESIS_PHASE +#[test] +fn test_register_after_genesis() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &ManagedVec::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.register( + &BLSKey::random(), + &ManagedVec::new(), + Some(REGISTRATIONS_DISABLED_GENESIS_PHASE), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_OK +/// +/// ### ACTION +/// Call 'register_validator()' after genesis phase +/// +/// ### EXPECTED +/// Validator is registered successfully after genesis +#[test] +fn test_register_validator_after_genesis() { + let mut state = ChainConfigTestState::new(); + + let first_token_stake_arg = StakeArgs { + token_identifier: FIRST_TEST_TOKEN.to_token_identifier(), + amount: BigUint::from(100u64), + }; + + let additional_stage_args = ManagedVec::from(vec![first_token_stake_arg]); + + let config = SovereignConfig { + max_validators: 3, + opt_additional_stake_required: Some(additional_stage_args), + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); + + let payment = EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_bytes()), + 0, + BigUint::from(100u64), + ); + + let mut payments_vec = MultiEgldOrEsdtPayment::new(); + payments_vec.push(payment); + + let num_of_validators: u64 = 3; + let dummy_message = ManagedBuffer::new_from_bytes(b"dummy message"); + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(num_of_validators as usize, &dummy_message); + + state + .common_setup + .register(&pub_keys[0], &payments_vec, None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(num_of_validators); + let epoch = 0; + + for id in 2..=num_of_validators { + let validator_data = ValidatorData { + id: BigUint::from(id as u32), + address: OWNER_ADDRESS.to_managed_address(), + bls_key: pub_keys[(id - 1) as usize].clone(), + }; + state.common_setup.register_validator_operation( + validator_data, + signature.clone(), + bitmap.clone(), + epoch, + ); + } +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_ERROR +/// +/// ### ACTION +/// Call 'register()' twice with and without additional stake +/// +/// ### EXPECTED +/// Successful register and INVALID_ADDITIONAL_STAKE +#[test] +fn test_register_additional_stake() { + let mut state = ChainConfigTestState::new(); + + let first_token_stake_arg = StakeArgs { + token_identifier: FIRST_TEST_TOKEN.to_token_identifier(), + amount: BigUint::from(ONE_HUNDRED_MILLION), + }; + + let additional_stage_args = ManagedVec::from(vec![first_token_stake_arg]); + + let config = SovereignConfig { + max_validators: 2, + opt_additional_stake_required: Some(additional_stage_args), + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); + + let payment = EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_bytes()), + 0, + BigUint::from(ONE_HUNDRED_MILLION), + ); + + let mut payments_with_additional_stake = MultiEgldOrEsdtPayment::new(); + payments_with_additional_stake.push(payment); + state + .common_setup + .register(&BLSKey::random(), &payments_with_additional_stake, None); + + let payments_no_additional_stake = MultiEgldOrEsdtPayment::new(); + state.common_setup.register( + &BLSKey::random(), + &payments_no_additional_stake, + Some(INVALID_ADDITIONAL_STAKE), + ); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_FAIL +/// +/// ### ACTION +/// Call 'unregister()' with not registered validator +/// +/// ### EXPECTED +/// Error VALIDATOR_NOT_REGISTERED +#[test] +fn test_unregister_not_registered() { + let mut state = ChainConfigTestState::new(); + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let bls_key = BLSKey::random(); + state + .common_setup + .unregister(&bls_key, Some(VALIDATOR_NOT_REGISTERED)); + + assert!(state.common_setup.get_bls_key_id(&bls_key) == 0); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_FAIL +/// +/// ### ACTION +/// Call 'unregister()' with registered BLS key but wrong caller +/// +/// ### EXPECTED +/// Error INVALID_BLS_KEY_FOR_CALLER +#[test] +fn test_unregister_wrong_caller_for_bls_key() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let new_validator_bls_key = BLSKey::random(); + state + .common_setup + .register(&new_validator_bls_key, &ManagedVec::new(), None); + + assert_eq!(state.common_setup.get_bls_key_id(&new_validator_bls_key), 1); + + state.unregister_with_caller( + &new_validator_bls_key, + USER_ADDRESS, + Some(INVALID_BLS_KEY_FOR_CALLER), + ); +} + +/// ### TEST +/// C-CONFIG_REGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'unregister()' with invalid BLS key +/// +/// ### EXPECTED +/// Error INVALID_BLS_KEY_PROVIDED +#[test] +fn test_unregister_invalid_bls_key() { + let mut state = ChainConfigTestState::new(); + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state.common_setup.unregister( + &ManagedBuffer::from("invalid bls key"), + Some(INVALID_BLS_KEY_PROVIDED), + ); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_OK +/// +/// ### ACTION +/// Call 'unregister()' with registered validator, no stake required +/// +/// ### EXPECTED +/// Validator is unregistered successfully +#[test] +fn test_unregister_no_stake() { + let mut state = ChainConfigTestState::new(); + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let validator_bls_key = BLSKey::random(); + state + .common_setup + .register(&validator_bls_key, &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.unregister(&validator_bls_key, None); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_OK +/// +/// ### ACTION +/// Call 'unregister()' with registered validator with both EGLD and ESDT stake +/// +/// ### EXPECTED +/// Validator is unregistered successfully and stake is returned +#[test] +fn test_unregister() { + let mut state = ChainConfigTestState::new(); + + let stake_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + let mut additional_stake_vec = ManagedVec::new(); + additional_stake_vec.push(StakeArgs { + token_identifier: FIRST_TEST_TOKEN.to_token_identifier(), + amount: stake_amount.clone(), + }); + + let config = SovereignConfig { + min_validators: 0, + max_validators: 2, + min_stake: stake_amount.clone(), + opt_additional_stake_required: Some(additional_stake_vec), + }; + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); + + let payments = state.common_setup.combined_stake_payments(&stake_amount); + + let new_validator_bls_key = BLSKey::random(); + state + .common_setup + .register(&new_validator_bls_key, &payments, None); + assert_eq!(state.common_setup.get_bls_key_id(&new_validator_bls_key), 1); + + let owner_initial_egld = BigUint::from(OWNER_BALANCE); + let owner_initial_token = BigUint::from(ONE_HUNDRED_MILLION); + let expected_owner_egld = owner_initial_egld.clone() - stake_amount.clone(); + let expected_owner_token = owner_initial_token.clone() - stake_amount.clone(); + state.common_setup.assert_contract_and_owner_balances( + &stake_amount, + &stake_amount, + &expected_owner_egld, + &expected_owner_token, + ); + + state.common_setup.unregister(&new_validator_bls_key, None); + + let zero = BigUint::zero(); + state.common_setup.assert_contract_and_owner_balances( + &zero, + &zero, + &owner_initial_egld, + &owner_initial_token, + ); + + assert_eq!(state.common_setup.get_bls_key_id(&new_validator_bls_key), 0); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'unregister()' after genesis phase +/// +/// ### EXPECTED +/// Error REGISTRATIONS_DISABLED_GENESIS_PHASE +#[test] +fn test_unregister_after_genesis() { + let mut state = ChainConfigTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &ManagedVec::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.unregister( + &BLSKey::random(), + Some(REGISTRATIONS_DISABLED_GENESIS_PHASE), + ); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_VALIDATOR_OK +/// +/// ### ACTION +/// Call 'unregister_validator()' after genesis phase +/// +/// ### EXPECTED +/// Validator is unregistered successfully after genesis +#[test] +fn test_unregister_validator_after_genesis() { + let mut state = ChainConfigTestState::new(); + + let token_amount = BigUint::from(100_000u64); + let first_token_stake_arg = StakeArgs { + token_identifier: FIRST_TEST_TOKEN.to_token_identifier(), + amount: token_amount.clone(), + }; + + let num_of_validators = 3; + let additional_stage_args = ManagedVec::from(vec![first_token_stake_arg]); + let config = SovereignConfig { + max_validators: num_of_validators, + opt_additional_stake_required: Some(additional_stage_args), + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(config), None); + + let payments = state.common_setup.single_token_payment(&token_amount); + let registered_bls_keys = state + .common_setup + .register_validators(num_of_validators, &payments); + + let expected_token_amount = token_amount.clone() * num_of_validators; + let owner_token_after_stake = + BigUint::from(ONE_HUNDRED_MILLION) - expected_token_amount.clone(); + state + .common_setup + .assert_contract_and_owner_token_balances(&expected_token_amount, &owner_token_after_stake); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(num_of_validators); + let epoch = 0; + + for (index, validator_bls_key) in registered_bls_keys.iter().enumerate() { + let validator_id = (index + 1) as u32; + + state + .common_setup + .unregister_validator_via_bridge_operation( + validator_id, + validator_bls_key, + num_of_validators, + &bitmap, + epoch, + ); + + assert_eq!(state.common_setup.get_bls_key_id(validator_bls_key), 0); + } + + let zero = BigUint::zero(); + let owner_initial_token = BigUint::from(ONE_HUNDRED_MILLION); + state + .common_setup + .assert_contract_and_owner_token_balances(&zero, &owner_initial_token); +} + +/// ### TEST +/// C-CONFIG_UNREGISTER_VALIDATOR_FAIL +/// +/// ### ACTION +/// Call 'unregister_validator()' after genesis with invalid data +/// +/// ### EXPECTED +/// Errors: VALIDATOR_ID_NOT_REGISTERED and INVALID_VALIDATOR_DATA +#[test] +fn test_unregister_validator_invalid() { + let mut state = ChainConfigTestState::new(); + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let signature = ManagedBuffer::new(); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + // invalid validator id + let validator1_bls_key = BLSKey::random(); + let validator_data_invalid_id = ValidatorData { + id: BigUint::from(999u32), + address: OWNER_ADDRESS.to_managed_address(), + bls_key: validator1_bls_key.clone(), + }; + state.common_setup.unregister_validator_operation( + validator_data_invalid_id, + signature.clone(), + bitmap.clone(), + epoch, + Some(VALIDATOR_ID_NOT_REGISTERED), + ); + + assert_eq!(state.common_setup.get_bls_key_id(&validator1_bls_key), 0); + + // invalid validator address for id + let validator2_bls_key = BLSKey::random(); + let validator_data_invalid_address = ValidatorData { + id: BigUint::from(1u32), + address: USER_ADDRESS.to_managed_address(), + bls_key: validator2_bls_key.clone(), + }; + state.common_setup.unregister_validator_operation( + validator_data_invalid_address, + signature.clone(), + bitmap.clone(), + epoch, + Some(INVALID_VALIDATOR_DATA), + ); + + assert_eq!(state.common_setup.get_bls_key_id(&validator2_bls_key), 0); +} diff --git a/chain-config/wasm/Cargo.lock b/chain-config/wasm/Cargo.lock index ae60f70d3..30457c1c4 100644 --- a/chain-config/wasm/Cargo.lock +++ b/chain-config/wasm/Cargo.lock @@ -10,23 +10,29 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "chain-config" version = "0.1.0" dependencies = [ + "common-utils", + "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", + "proxies", + "setup-phase", + "structs", ] [[package]] @@ -37,11 +43,44 @@ dependencies = [ "multiversx-sc-wasm-adapter", ] +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", +] + +[[package]] +name = "cross-chain" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "multiversx-sc", + "multiversx-sc-modules", + "proxies", + "structs", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", +] + [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" @@ -64,15 +103,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -80,9 +119,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -96,9 +135,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -108,9 +147,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -120,9 +159,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -133,18 +172,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -169,43 +208,68 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] +[[package]] +name = "proxies" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "structs", +] + [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", ] +[[package]] +name = "setup-phase" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "multiversx-sc", +] + [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "structs" +version = "0.1.0" +dependencies = [ + "multiversx-sc", +] [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -220,9 +284,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" diff --git a/chain-config/wasm/Cargo.toml b/chain-config/wasm/Cargo.toml index 9bfbc32a3..510150418 100644 --- a/chain-config/wasm/Cargo.toml +++ b/chain-config/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/chain-config/wasm/src/lib.rs b/chain-config/wasm/src/lib.rs index b69bd79d1..0078d9a2c 100644 --- a/chain-config/wasm/src/lib.rs +++ b/chain-config/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 10 +// Endpoints: 11 // Async Callback (empty): 1 -// Total number of exported functions: 13 +// Total number of exported functions: 14 #![no_std] @@ -20,16 +20,17 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - deployBridge => deploy_bridge - getMinValidators => min_validators - getMaxValidators => max_validators - getMinStake => min_stake - getAdditionalStakeRequired => additional_stake_required - wasPreviouslySlashed => was_previously_slashed - isAdmin => is_admin - addAdmin => add_admin - removeAdmin => remove_admin - getAdmins => admins + completeSetupPhase => complete_setup_phase + register => register + registerBlsKey => register_bls_key + unregister => unregister + unregisterBlsKey => unregister_bls_key + sovereignConfig => sovereign_config + blsKeyToId => bls_key_to_id_mapper + validator_info => validator_info + blsKeysMap => bls_keys_map + updateSovereignConfigSetupPhase => update_sovereign_config_during_setup_phase + updateSovereignConfig => update_sovereign_config ) } diff --git a/chain-factory/Cargo.toml b/chain-factory/Cargo.toml index 18658332b..b7368bf57 100644 --- a/chain-factory/Cargo.toml +++ b/chain-factory/Cargo.toml @@ -12,16 +12,31 @@ path = "src/lib.rs" num-bigint = "0.4.2" [dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" +version = "0.63.0" + +[dev-dependencies.common-test-setup] +path = "../common/common-test-setup" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" + +[dependencies.multiversx-sc-modules] +version = "0.63.0" -[dependencies.utils] -path = "../common/utils" +[dependencies.common-utils] +path = "../common/common-utils" [dependencies.chain-config] path = "../chain-config" +[dependencies.proxies] +path = "../common/proxies" + +[dependencies.structs] +path = "../common/structs/" + [dependencies.error-messages] path = "../common/error-messages" + +[dependencies.custom-events] +path = "../common/custom-events" diff --git a/chain-factory/README.md b/chain-factory/README.md new file mode 100644 index 000000000..25507b52c --- /dev/null +++ b/chain-factory/README.md @@ -0,0 +1,13 @@ +# Chain Factory Contract + +Factory that clones the sovereign contract templates on MultiversX and wires ownership to the correct controllers. + +- **Initialization:** `init` receives the Sovereign Forge address plus template addresses for Chain Config, Header Verifier, MultiversX ESDT Safe, and Fee Market. Templates are required to be valid smart contract addresses. +- **Deploying per sovereign:** Only admins (typically the Sovereign Forge) may call: + - `deploySovereignChainConfigContract(opt_config)` + - `deployEsdtSafe(sovereign_owner, sov_prefix, opt_config)` + - `deployFeeMarket(esdt_safe_address, fee)` + - `deployHeaderVerifier(sovereign_contracts)` + Each returns the fresh contract address. +- **Completing setup:** `completeSetupPhase` calls `completeSetupPhase` on Chain Config, then transfers ownership of Chain Config, MultiversX ESDT Safe, and Fee Market to the Header Verifier, and finally completes their setup phases. This makes the Header Verifier the gatekeeper for bridge operations. +- **Interactions:** Sovereign Forge drives deployments through this factory. After phase four, the factory’s setup completion hooks finish wiring the sovereign’s contract suite. diff --git a/chain-factory/meta/Cargo.toml b/chain-factory/meta/Cargo.toml index 57043dc25..4d4d1100f 100644 --- a/chain-factory/meta/Cargo.toml +++ b/chain-factory/meta/Cargo.toml @@ -11,4 +11,4 @@ authors = ["you"] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "=0.57.1" +version = "0.63.0" diff --git a/chain-factory/sc-config.toml b/chain-factory/sc-config.toml new file mode 100644 index 000000000..9ffeb00ad --- /dev/null +++ b/chain-factory/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "../common/proxies/src/chain_factory_proxy.rs" diff --git a/chain-factory/src/complete_phases.rs b/chain-factory/src/complete_phases.rs new file mode 100644 index 000000000..bdc966dda --- /dev/null +++ b/chain-factory/src/complete_phases.rs @@ -0,0 +1,68 @@ +use crate::err_msg; +use multiversx_sc::imports::UserBuiltinProxy; +use multiversx_sc_modules::only_admin; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, header_verifier_proxy::HeaderverifierProxy, + mvx_esdt_safe_proxy::MvxEsdtSafeProxy, mvx_fee_market_proxy::MvxFeeMarketProxy, +}; + +#[multiversx_sc::module] +pub trait CompletePhasesModule: only_admin::OnlyAdminModule { + #[only_admin] + #[endpoint(completeSetupPhase)] + fn complete_setup_phase( + &self, + chain_config_address: ManagedAddress, + header_verifier_address: ManagedAddress, + mvx_esdt_safe_address: ManagedAddress, + fee_market_address: ManagedAddress, + ) { + self.tx() + .to(&chain_config_address) + .typed(ChainConfigContractProxy) + .complete_setup_phase() + .sync_call(); + + self.tx() + .to(&chain_config_address) + .typed(UserBuiltinProxy) + .change_owner_address(&header_verifier_address) + .sync_call(); + + self.tx() + .to(&header_verifier_address) + .typed(HeaderverifierProxy) + .complete_setup_phase() + .sync_call(); + + self.tx() + .to(&header_verifier_address) + .typed(UserBuiltinProxy) + .change_owner_address(&header_verifier_address) + .sync_call(); + + self.tx() + .to(&mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() + .sync_call(); + + self.tx() + .to(&mvx_esdt_safe_address) + .typed(UserBuiltinProxy) + .change_owner_address(&header_verifier_address) + .sync_call(); + + self.tx() + .to(&fee_market_address) + .typed(MvxFeeMarketProxy) + .complete_setup_phase() + .sync_call(); + + self.tx() + .to(&fee_market_address) + .typed(UserBuiltinProxy) + .change_owner_address(&header_verifier_address) + .sync_call(); + } +} diff --git a/chain-factory/src/factory.rs b/chain-factory/src/factory.rs index 75988e71d..e5f3231f4 100644 --- a/chain-factory/src/factory.rs +++ b/chain-factory/src/factory.rs @@ -1,58 +1,120 @@ -use chain_config::StakeMultiArg; -use error_messages::INVALID_PAYMENT_AMOUNT; - -multiversx_sc::imports!(); +use multiversx_sc::imports::*; +use multiversx_sc_modules::only_admin; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, header_verifier_proxy::HeaderverifierProxy, + mvx_esdt_safe_proxy::MvxEsdtSafeProxy, mvx_fee_market_proxy::MvxFeeMarketProxy, +}; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, + forge::ContractInfo, +}; +multiversx_sc::derive_imports!(); #[multiversx_sc::module] -pub trait FactoryModule { - #[payable("EGLD")] +pub trait FactoryModule: only_admin::OnlyAdminModule { + #[only_admin] #[endpoint(deploySovereignChainConfigContract)] fn deploy_sovereign_chain_config_contract( &self, - min_validators: usize, - max_validators: usize, - min_stake: BigUint, - additional_stake_required: MultiValueEncoded>, - ) { - let payment_amount = self.call_value().egld().clone_value(); - let deploy_cost = self.deploy_cost().get(); - require!(payment_amount == deploy_cost, INVALID_PAYMENT_AMOUNT); - - let caller = self.blockchain().get_caller(); + opt_config: OptionalValue>, + ) -> ManagedAddress { let source_address = self.chain_config_template().get(); - let metadata = - CodeMetadata::PAYABLE_BY_SC | CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE; + let metadata = self.blockchain().get_code_metadata(&source_address); - let (sc_address, _) = self - .chain_config_proxy() - .init( - min_validators, - max_validators, - min_stake, - caller, - additional_stake_required, - ) - .deploy_from_source::(&source_address, metadata); + self.tx() + .typed(ChainConfigContractProxy) + .init(opt_config) + .from_source(source_address) + .code_metadata(metadata) + .returns(ReturnsNewManagedAddress) + .sync_call() + } - let _ = self.all_deployed_contracts().insert(sc_address); + #[only_admin] + #[endpoint(deployHeaderVerifier)] + fn deploy_header_verifier( + &self, + sovereign_contracts: MultiValueEncoded>, + ) -> ManagedAddress { + let source_address = self.header_verifier_template().get(); + let metadata = self.blockchain().get_code_metadata(&source_address); + + self.tx() + .typed(HeaderverifierProxy) + .init(sovereign_contracts) + .from_source(source_address) + .code_metadata(metadata) + .returns(ReturnsNewManagedAddress) + .sync_call() } - #[only_owner] - #[endpoint(blacklistSovereignChainSc)] - fn blacklist_sovereign_chain_sc(&self, sc_address: ManagedAddress) { - let _ = self.all_deployed_contracts().swap_remove(&sc_address); + #[only_admin] + #[endpoint(deployEsdtSafe)] + fn deploy_mvx_esdt_safe( + &self, + sovereign_owner: ManagedAddress, + sov_token_prefix: ManagedBuffer, + opt_config: OptionalValue>, + ) -> ManagedAddress { + let source_address = self.esdt_safe_template().get(); + let metadata = self.blockchain().get_code_metadata(&source_address); + + self.tx() + .typed(MvxEsdtSafeProxy) + .init( + sovereign_owner, + self.blockchain().get_caller(), + sov_token_prefix, + opt_config, + ) + .from_source(source_address) + .code_metadata(metadata) + .returns(ReturnsNewManagedAddress) + .sync_call() } - #[proxy] - fn chain_config_proxy(&self) -> chain_config::Proxy; + #[only_admin] + #[endpoint(deployFeeMarket)] + fn deploy_fee_market( + &self, + esdt_safe_address: ManagedAddress, + fee: Option>, + ) -> ManagedAddress { + let source_address = self.fee_market_template().get(); + let metadata = self.blockchain().get_code_metadata(&source_address); + + let fee_market_address = self + .tx() + .typed(MvxFeeMarketProxy) + .init(&esdt_safe_address, fee) + .from_source(source_address) + .code_metadata(metadata) + .returns(ReturnsNewManagedAddress) + .sync_call(); - #[view(getDeployCost)] - #[storage_mapper("deployCost")] - fn deploy_cost(&self) -> SingleValueMapper; + self.tx() + .to(&esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .set_fee_market_address(&fee_market_address) + .sync_call(); + fee_market_address + } + + #[view(getChainConfigTemplateAddress)] #[storage_mapper("chainConfigTemplate")] fn chain_config_template(&self) -> SingleValueMapper; - #[storage_mapper("allDeployedContracts")] - fn all_deployed_contracts(&self) -> UnorderedSetMapper; + #[view(getHeaderVerifierTemplateAddress)] + #[storage_mapper("headerVerifierTemplate")] + fn header_verifier_template(&self) -> SingleValueMapper; + + #[view(getEsdtSafeTemplateAddress)] + #[storage_mapper("esdtSafeTemplate")] + fn esdt_safe_template(&self) -> SingleValueMapper; + + #[view(getFeeMarketTemplateAddress)] + #[storage_mapper("feeMarketTemplate")] + fn fee_market_template(&self) -> SingleValueMapper; } diff --git a/chain-factory/src/lib.rs b/chain-factory/src/lib.rs index 13dfa37d3..18ed71798 100644 --- a/chain-factory/src/lib.rs +++ b/chain-factory/src/lib.rs @@ -1,28 +1,43 @@ #![no_std] +use multiversx_sc_modules::only_admin; + multiversx_sc::imports!(); +pub mod complete_phases; pub mod factory; -pub mod slash; +pub mod update_configs; #[multiversx_sc::contract] pub trait ChainFactoryContract: - factory::FactoryModule + slash::SlashModule + utils::UtilsModule + factory::FactoryModule + + common_utils::CommonUtilsModule + + only_admin::OnlyAdminModule + + update_configs::UpdateConfigsModule + + complete_phases::CompletePhasesModule + + custom_events::CustomEventsModule { #[init] fn init( &self, - validators_contract_address: ManagedAddress, + sovereign_forge_address: ManagedAddress, chain_config_template: ManagedAddress, - deploy_cost: BigUint, + header_verifier_template: ManagedAddress, + mvx_esdt_safe_template: ManagedAddress, + fee_market_template: ManagedAddress, ) { - self.require_sc_address(&validators_contract_address); + self.require_sc_address(&sovereign_forge_address); self.require_sc_address(&chain_config_template); + self.require_sc_address(&header_verifier_template); + self.require_sc_address(&mvx_esdt_safe_template); + self.require_sc_address(&fee_market_template); - self.validators_contract_address() - .set(validators_contract_address); + self.add_admin(sovereign_forge_address); self.chain_config_template().set(chain_config_template); - self.deploy_cost().set(deploy_cost); + self.header_verifier_template() + .set(header_verifier_template); + self.esdt_safe_template().set(mvx_esdt_safe_template); + self.fee_market_template().set(fee_market_template); } #[upgrade] diff --git a/chain-factory/src/slash.rs b/chain-factory/src/slash.rs deleted file mode 100644 index e23e4f657..000000000 --- a/chain-factory/src/slash.rs +++ /dev/null @@ -1,63 +0,0 @@ -use error_messages::ONLY_DEPLOYED_CONTRACTS_CAN_CALL_ENDPOINT; - -multiversx_sc::imports!(); - -pub type DestAmountPairs = MultiValueEncoded, BigUint>>; - -mod validators_contract_proxy { - use super::DestAmountPairs; - - multiversx_sc::imports!(); - - #[multiversx_sc::proxy] - pub trait ValidatorsContractProxy { - #[endpoint] - fn slash(&self, validator_address: ManagedAddress, value: BigUint); - - #[endpoint(distributeSlashed)] - fn distribute_slashed(&self, dest_amount_pairs: DestAmountPairs); - } -} - -#[multiversx_sc::module] -pub trait SlashModule: crate::factory::FactoryModule { - #[endpoint] - fn slash(&self, validator_address: ManagedAddress, value: BigUint) { - let caller = self.blockchain().get_caller(); - self.require_deployed_sc(&caller); - - let validators_contract_address = self.validators_contract_address().get(); - let _: IgnoreValue = self - .validator_proxy(validators_contract_address) - .slash(validator_address, value) - .execute_on_dest_context(); - } - - #[endpoint(distributeSlashed)] - fn distribute_slashed(&self, dest_amount_pairs: DestAmountPairs) { - let caller = self.blockchain().get_caller(); - self.require_deployed_sc(&caller); - - let validators_contract_address = self.validators_contract_address().get(); - let _: IgnoreValue = self - .validator_proxy(validators_contract_address) - .distribute_slashed(dest_amount_pairs) - .execute_on_dest_context(); - } - - fn require_deployed_sc(&self, address: &ManagedAddress) { - require!( - self.all_deployed_contracts().contains(address), - ONLY_DEPLOYED_CONTRACTS_CAN_CALL_ENDPOINT - ); - } - - #[proxy] - fn validator_proxy( - &self, - sc_address: ManagedAddress, - ) -> validators_contract_proxy::Proxy; - - #[storage_mapper("validatorsContractAddress")] - fn validators_contract_address(&self) -> SingleValueMapper; -} diff --git a/chain-factory/src/update_configs.rs b/chain-factory/src/update_configs.rs new file mode 100644 index 000000000..160617113 --- /dev/null +++ b/chain-factory/src/update_configs.rs @@ -0,0 +1,122 @@ +use crate::err_msg; +use multiversx_sc::types::{EsdtTokenIdentifier, MultiValueEncoded}; +use multiversx_sc_modules::only_admin; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, mvx_esdt_safe_proxy::MvxEsdtSafeProxy, + mvx_fee_market_proxy::MvxFeeMarketProxy, +}; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, +}; + +#[multiversx_sc::module] +pub trait UpdateConfigsModule: only_admin::OnlyAdminModule { + #[only_admin] + #[endpoint(updateEsdtSafeConfig)] + fn update_esdt_safe_config( + &self, + esdt_safe_address: ManagedAddress, + new_config: EsdtSafeConfig, + ) { + self.tx() + .to(esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .update_esdt_safe_config_during_setup_phase(new_config) + .sync_call(); + } + + #[only_admin] + #[endpoint(updateSovereignConfig)] + fn update_sovereign_config( + &self, + chain_config_address: ManagedAddress, + new_config: SovereignConfig, + ) { + self.tx() + .to(chain_config_address) + .typed(ChainConfigContractProxy) + .update_sovereign_config_during_setup_phase(new_config) + .sync_call(); + } + + #[only_admin] + #[endpoint(setFee)] + fn set_fee(&self, fee_market_address: ManagedAddress, new_fee: FeeStruct) { + self.tx() + .to(fee_market_address) + .typed(MvxFeeMarketProxy) + .set_fee_during_setup_phase(new_fee) + .sync_call(); + } + + #[only_admin] + #[endpoint(removeFee)] + fn remove_fee( + &self, + fee_market_address: ManagedAddress, + token_id: EsdtTokenIdentifier, + ) { + self.tx() + .to(fee_market_address) + .typed(MvxFeeMarketProxy) + .remove_fee_during_setup_phase(token_id) + .sync_call(); + } + + #[only_admin] + #[endpoint(addUsersToWhitelistSetupPhase)] + fn add_users_to_whitelist( + &self, + fee_market_address: ManagedAddress, + users: MultiValueEncoded, + ) { + self.tx() + .to(fee_market_address) + .typed(MvxFeeMarketProxy) + .add_users_to_whitelist_during_setup_phase(users) + .sync_call(); + } + + #[only_admin] + #[endpoint(removeUsersFromWhitelistSetupPhase)] + fn remove_users_from_whitelist( + &self, + fee_market_address: ManagedAddress, + users: MultiValueEncoded, + ) { + self.tx() + .to(fee_market_address) + .typed(MvxFeeMarketProxy) + .remove_users_from_whitelist_during_setup_phase(users) + .sync_call(); + } + + #[only_admin] + #[endpoint(setTokenBurnMechanismSetupPhase)] + fn set_token_burn_mechanism( + &self, + mvx_esdt_safe_address: ManagedAddress, + token_id: EgldOrEsdtTokenIdentifier, + ) { + self.tx() + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .set_token_burn_mechanism_setup_phase(token_id) + .sync_call(); + } + + #[only_admin] + #[endpoint(setTokenLockMechanismSetupPhase)] + fn set_token_lock_mechanism( + &self, + mvx_esdt_safe_address: ManagedAddress, + token_id: EgldOrEsdtTokenIdentifier, + ) { + self.tx() + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .set_token_lock_mechanism_setup_phase(token_id) + .sync_call(); + } +} diff --git a/chain-factory/tests/chain_factory_blackbox_setup.rs b/chain-factory/tests/chain_factory_blackbox_setup.rs new file mode 100644 index 000000000..b2b277ba8 --- /dev/null +++ b/chain-factory/tests/chain_factory_blackbox_setup.rs @@ -0,0 +1,51 @@ +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + constants::{ + CHAIN_FACTORY_SC_ADDRESS, OWNER_ADDRESS, OWNER_BALANCE, SOVEREIGN_FORGE_SC_ADDRESS, + }, +}; +use multiversx_sc::{imports::OptionalValue, types::ReturnsHandledOrError}; +use multiversx_sc_scenario::imports::*; +use proxies::chain_factory_proxy::ChainFactoryContractProxy; +use structs::configs::SovereignConfig; + +pub struct ChainFactoryTestState { + pub common_setup: BaseSetup, +} + +impl ChainFactoryTestState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let owner_account = AccountSetup { + address: OWNER_ADDRESS.to_address(), + code_path: None, + egld_balance: Some(OWNER_BALANCE.into()), + esdt_balances: None, + }; + + let account_setups = vec![owner_account]; + + let common_setup = BaseSetup::new(account_setups); + + Self { common_setup } + } + + pub fn deploy_chain_config_from_factory( + &mut self, + opt_config: OptionalValue>, + ) { + let response = self + .common_setup + .world + .tx() + .from(SOVEREIGN_FORGE_SC_ADDRESS) + .to(CHAIN_FACTORY_SC_ADDRESS) + .typed(ChainFactoryContractProxy) + .deploy_sovereign_chain_config_contract(opt_config) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + } +} diff --git a/chain-factory/tests/chain_factory_blackbox_tests.rs b/chain-factory/tests/chain_factory_blackbox_tests.rs new file mode 100644 index 000000000..af562f200 --- /dev/null +++ b/chain-factory/tests/chain_factory_blackbox_tests.rs @@ -0,0 +1,34 @@ +use chain_factory_blackbox_setup::ChainFactoryTestState; +use multiversx_sc::imports::OptionalValue; + +mod chain_factory_blackbox_setup; + +#[test] +fn test_deploy() { + let mut state = ChainFactoryTestState::new(); + state.common_setup.deploy_chain_factory(); +} + +/// ### TEST +/// C-FACTORY_DEPLOY_CHAIN_CONFIG_OK +/// +/// ### ACTION +/// Call 'deploy_chain_config_from_factory()' with a valid config +/// +/// ### EXPECTED +/// Chain config is deployed correctly +#[test] +fn test_deploy_chain_config_from_factory() { + let mut state = ChainFactoryTestState::new(); + + state + .common_setup + .deploy_sovereign_forge(OptionalValue::None); + state + .common_setup + .deploy_chain_config(OptionalValue::None, None); + + state.common_setup.deploy_chain_factory(); + + state.deploy_chain_config_from_factory(OptionalValue::None); +} diff --git a/chain-factory/wasm/Cargo.lock b/chain-factory/wasm/Cargo.lock index a48fc962a..48d3877eb 100644 --- a/chain-factory/wasm/Cargo.lock +++ b/chain-factory/wasm/Cargo.lock @@ -10,23 +10,29 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "chain-config" version = "0.1.0" dependencies = [ + "common-utils", + "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", + "proxies", + "setup-phase", + "structs", ] [[package]] @@ -34,9 +40,13 @@ name = "chain-factory" version = "0.1.0" dependencies = [ "chain-config", + "common-utils", + "custom-events", "error-messages", "multiversx-sc", - "utils", + "multiversx-sc-modules", + "proxies", + "structs", ] [[package]] @@ -47,11 +57,44 @@ dependencies = [ "multiversx-sc-wasm-adapter", ] +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", +] + +[[package]] +name = "cross-chain" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "multiversx-sc", + "multiversx-sc-modules", + "proxies", + "structs", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", +] + [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" @@ -74,15 +117,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -90,9 +133,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -106,9 +149,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -118,9 +161,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -130,9 +173,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -143,18 +186,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -179,37 +222,55 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] +[[package]] +name = "proxies" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "structs", +] + [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", ] +[[package]] +name = "setup-phase" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "multiversx-sc", +] + [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "structs" @@ -220,9 +281,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -237,21 +298,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/chain-factory/wasm/Cargo.toml b/chain-factory/wasm/Cargo.toml index 73eee9313..5b8ba3f55 100644 --- a/chain-factory/wasm/Cargo.toml +++ b/chain-factory/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/chain-factory/wasm/src/lib.rs b/chain-factory/wasm/src/lib.rs index cd93df9de..e8f6357ac 100644 --- a/chain-factory/wasm/src/lib.rs +++ b/chain-factory/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 5 +// Endpoints: 21 // Async Callback (empty): 1 -// Total number of exported functions: 8 +// Total number of exported functions: 24 #![no_std] @@ -21,10 +21,26 @@ multiversx_sc_wasm_adapter::endpoints! { init => init upgrade => upgrade deploySovereignChainConfigContract => deploy_sovereign_chain_config_contract - blacklistSovereignChainSc => blacklist_sovereign_chain_sc - getDeployCost => deploy_cost - slash => slash - distributeSlashed => distribute_slashed + deployHeaderVerifier => deploy_header_verifier + deployEsdtSafe => deploy_mvx_esdt_safe + deployFeeMarket => deploy_fee_market + getChainConfigTemplateAddress => chain_config_template + getHeaderVerifierTemplateAddress => header_verifier_template + getEsdtSafeTemplateAddress => esdt_safe_template + getFeeMarketTemplateAddress => fee_market_template + isAdmin => is_admin + addAdmin => add_admin + removeAdmin => remove_admin + getAdmins => admins + updateEsdtSafeConfig => update_esdt_safe_config + updateSovereignConfig => update_sovereign_config + setFee => set_fee + removeFee => remove_fee + addUsersToWhitelistSetupPhase => add_users_to_whitelist + removeUsersFromWhitelistSetupPhase => remove_users_from_whitelist + setTokenBurnMechanismSetupPhase => set_token_burn_mechanism + setTokenLockMechanismSetupPhase => set_token_lock_mechanism + completeSetupPhase => complete_setup_phase ) } diff --git a/common/common-interactor/Cargo.toml b/common/common-interactor/Cargo.toml new file mode 100644 index 000000000..451ba5862 --- /dev/null +++ b/common/common-interactor/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "common-interactor" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies] +toml = "0.8" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.multiversx-sc-snippets] +version = "0.63.0" + +[dependencies.common-test-setup] +path = "../common-test-setup" + +[dependencies.structs] +path = "../structs" + +[dependencies.proxies] +path = "../proxies" + +[dependencies.error-messages] +path = "../error-messages" + +[dependencies.base64] +version = "0.21.0" +default-features = false +features = ["alloc"] + +[dependencies.multiversx-bls] +version = "0.2" + +[dependencies.serde] +version = "1.0" +features = ["derive"] + +[dependencies.rand] +version = "0.9.2" diff --git a/common/common-interactor/src/common_sovereign_interactor.rs b/common/common-interactor/src/common_sovereign_interactor.rs new file mode 100644 index 000000000..20574526d --- /dev/null +++ b/common/common-interactor/src/common_sovereign_interactor.rs @@ -0,0 +1,1725 @@ +#![allow(async_fn_in_trait)] +use crate::{ + interactor_helpers::InteractorHelpers, + interactor_state::{AddressInfo, EsdtTokenInfo}, + interactor_structs::{ActionConfig, IssueTokenStruct, MintTokenStruct, TemplateAddresses}, +}; +use common_test_setup::{ + base_setup::{init::ExpectedLogs, log_validations::assert_expected_logs}, + constants::{ + CHAIN_CONFIG_CODE_PATH, CHAIN_FACTORY_CODE_PATH, CHAIN_ID, DEPLOY_COST, + FAILED_TO_LOAD_WALLET_SHARD_0, FEE_MARKET_CODE_PATH, HEADER_VERIFIER_CODE_PATH, ISSUE_COST, + MVX_ESDT_SAFE_CODE_PATH, NATIVE_TOKEN_NAME, NATIVE_TOKEN_TICKER, NUMBER_OF_SHARDS, + NUM_TOKENS_TO_MINT, ONE_THOUSAND_TOKENS, SHARD_0, SOVEREIGN_FORGE_CODE_PATH, + SOVEREIGN_TOKEN_PREFIX, TESTING_SC_CODE_PATH, WALLET_SHARD_0, + }, +}; +use multiversx_bls::{SecretKey, G1}; +use multiversx_sc::{ + codec::num_bigint, + imports::{ESDTSystemSCProxy, OptionalValue, UserBuiltinProxy}, + types::{ + Address, BigUint, CodeMetadata, ESDTSystemSCAddress, EgldOrEsdtTokenIdentifier, + EsdtLocalRole, EsdtTokenIdentifier, EsdtTokenType, ManagedAddress, ManagedBuffer, + ManagedVec, MultiEgldOrEsdtPayment, MultiValueEncoded, ReturnsNewAddress, ReturnsResult, + ReturnsResultUnmanaged, + }, +}; +use multiversx_sc_snippets::{ + imports::{ + Bech32Address, ReturnsGasUsed, ReturnsHandledOrError, ReturnsLogs, + ReturnsNewTokenIdentifier, StaticApi, Wallet, + }, + multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256, + test_wallets, InteractorRunAsync, SimulateGas, +}; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, chain_factory_proxy::ChainFactoryContractProxy, + header_verifier_proxy::HeaderverifierProxy, mvx_esdt_safe_proxy::MvxEsdtSafeProxy, + mvx_fee_market_proxy::MvxFeeMarketProxy, sovereign_forge_proxy::SovereignForgeProxy, + testing_sc_proxy::TestingScProxy, +}; +use structs::{ + aliases::{OptionalValueTransferDataTuple, PaymentsVec, TxNonce}, + configs::{ + EsdtSafeConfig, PauseStatusOperation, SetBurnMechanismOperation, SetLockMechanismOperation, + SovereignConfig, UpdateEsdtSafeConfigOperation, + }, + fee::{FeeStruct, RemoveFeeOperation, SetFeeOperation}, + forge::{ContractInfo, ScArray}, + generate_hash::GenerateHash, + operation::Operation, + EsdtInfo, OperationHashStatus, RegisterTokenOperation, +}; + +fn metadata() -> CodeMetadata { + CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE +} + +pub trait CommonInteractorTrait: InteractorHelpers { + async fn register_wallets(&mut self) { + let wallet_path = WALLET_SHARD_0.to_string(); + let wallet = Wallet::from_pem_file(&wallet_path) + .unwrap_or_else(|_| panic!("{}", FAILED_TO_LOAD_WALLET_SHARD_0)); + + self.interactor().register_wallet(test_wallets::bob()).await; + self.interactor().register_wallet(test_wallets::dan()).await; + self.interactor() + .register_wallet(test_wallets::heidi()) + .await; + + self.interactor() + .register_wallet(test_wallets::mike()) + .await; + self.interactor().register_wallet(test_wallets::eve()).await; + self.interactor() + .register_wallet(test_wallets::judy()) + .await; + + self.interactor().register_wallet(wallet).await; + self.interactor() + .register_wallet(test_wallets::frank()) + .await; + self.interactor() + .register_wallet(test_wallets::carol()) + .await; + + self.interactor().generate_blocks(2u64).await.unwrap(); + } + + async fn issue_and_mint_token(&mut self, issue: IssueTokenStruct, mint: MintTokenStruct) { + let user_address = self.user_address().clone(); + let interactor = self.interactor(); + + let token_id = interactor + .tx() + .from(user_address) + .to(ESDTSystemSCAddress) + .gas(SimulateGas) + .typed(ESDTSystemSCProxy) + .issue_and_set_all_roles( + ISSUE_COST.into(), + issue.token_display_name, + issue.token_ticker.clone(), + issue.token_type, + issue.num_decimals, + ) + .returns(ReturnsNewTokenIdentifier) + .run() + .await; + + let num_mints = if issue.token_ticker == "TRUSTED" || issue.token_ticker == "FEE" { + 1 + } else { + NUM_TOKENS_TO_MINT + }; + + for _ in 0..num_mints { + let nonce = self + .mint_tokens(token_id.clone(), issue.token_type, mint.clone()) + .await; + + let decimals = self.get_token_decimals(issue.token_type); + + let token = EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(token_id.as_bytes()), + nonce, + token_type: issue.token_type, + decimals, + amount: mint.amount.clone(), + }; + + match issue.token_ticker.as_str() { + "MVX" => self.state().add_fungible_token(token.clone()), + "TRUSTED" => self.common_state().set_trusted_token(token_id.clone()), + "FEE" => self.state().set_fee_token(token.clone()), + "NFT" => self.state().add_nft_token(token.clone()), + "SFT" => self.state().add_sft_token(token.clone()), + "DYN" => self.state().add_dynamic_nft_token(token.clone()), + "META" => self.state().add_meta_esdt_token(token.clone()), + "DYNS" => self.state().add_dynamic_sft_token(token.clone()), + "DYNM" => self.state().add_dynamic_meta_esdt_token(token.clone()), + _ => {} + } + + self.state() + .update_or_add_initial_wallet_token(token.clone()); + } + } + + async fn mint_tokens( + &mut self, + token_id: String, + token_type: EsdtTokenType, + mint: MintTokenStruct, + ) -> u64 { + let user_address = self.user_address().clone(); + let interactor = self.interactor(); + let mint_base_tx = interactor + .tx() + .from(user_address.clone()) + .to(user_address) + .gas(SimulateGas) + .typed(UserBuiltinProxy); + + match token_type { + EsdtTokenType::Fungible => { + mint_base_tx + .esdt_local_mint( + EsdtTokenIdentifier::from(token_id.as_bytes()), + 0, + mint.amount, + ) + .returns(ReturnsResultUnmanaged) + .run() + .await; + 0u64 + } + EsdtTokenType::NonFungibleV2 + | EsdtTokenType::SemiFungible + | EsdtTokenType::DynamicNFT + | EsdtTokenType::DynamicMeta + | EsdtTokenType::DynamicSFT + | EsdtTokenType::MetaFungible => { + mint_base_tx + .esdt_nft_create( + EsdtTokenIdentifier::from(token_id.as_bytes()), + mint.amount, + mint.name.unwrap_or_default(), + BigUint::zero(), + ManagedBuffer::new(), + &mint.attributes.unwrap_or_default(), + &ManagedVec::new(), + ) + .returns(ReturnsResult) + .run() + .await + } + _ => { + panic!("Unsupported token type: {:?}", token_type); + } + } + } + + async fn create_token_with_config( + &mut self, + token_type: EsdtTokenType, + ticker: &str, + decimals: usize, + ) { + if ticker == "FEE" && !self.common_state().fee_market_tokens.is_empty() { + let fee_token = self.retrieve_current_fee_token_for_wallet().await; + self.state().set_fee_token(fee_token); + return; + } + if ticker == "TRUSTED" && self.common_state().trusted_token.is_some() { + let trusted_token = self.retrieve_current_trusted_token_for_wallet().await; + self.state().set_trusted_token(trusted_token.clone()); + self.state() + .update_or_add_initial_wallet_token(trusted_token.clone()); + return; + } + let amount = if matches!( + token_type, + EsdtTokenType::NonFungibleV2 | EsdtTokenType::DynamicNFT + ) { + BigUint::from(1u64) + } else { + BigUint::from(ONE_THOUSAND_TOKENS) + }; + let token_struct = IssueTokenStruct { + token_display_name: ticker.to_string(), + token_ticker: ticker.to_string(), + token_type, + num_decimals: decimals, + }; + + let mint_struct = MintTokenStruct { + name: if matches!(token_type, EsdtTokenType::Fungible) { + None + } else { + Some(ticker.to_string()) + }, + amount, + attributes: None, + }; + + self.issue_and_mint_token(token_struct, mint_struct).await; + } + + async fn deploy_sovereign_forge( + &mut self, + caller: Address, + deploy_cost: OptionalValue<&BigUint>, + ) -> Address { + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(SimulateGas) + .typed(SovereignForgeProxy) + .init(deploy_cost) + .code(SOVEREIGN_FORGE_CODE_PATH) + .code_metadata(metadata()) + .returns(ReturnsNewAddress) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state() + .set_sovereign_forge_sc_address(new_address_bech32.clone()); + + new_address + } + + async fn deploy_chain_factory( + &mut self, + caller: Address, + sovereign_forge_address: Address, + template_addresses: TemplateAddresses, + ) { + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(SimulateGas) + .typed(ChainFactoryContractProxy) + .init( + sovereign_forge_address, + template_addresses.chain_config_address, + template_addresses.header_verifier_address, + template_addresses.esdt_safe_address, + template_addresses.fee_market_address, + ) + .code(CHAIN_FACTORY_CODE_PATH) + .code_metadata(metadata()) + .returns(ReturnsNewAddress) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state() + .set_chain_factory_sc_address(new_address_bech32); + } + + async fn deploy_chain_config( + &mut self, + caller: Address, + chain_id: String, + opt_config: OptionalValue>, + ) { + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(SimulateGas) + .typed(ChainConfigContractProxy) + .init(opt_config) + .returns(ReturnsNewAddress) + .code(CHAIN_CONFIG_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state() + .set_chain_config_sc_address(AddressInfo { + address: new_address_bech32, + chain_id, + }); + } + + async fn deploy_template_contracts( + &mut self, + caller: Address, + forge_address: &Address, + chain_id: &str, + ) -> Vec { + let mut template_contracts = vec![]; + + let chain_config_template = self + .interactor() + .tx() + .from(caller.clone()) + .gas(SimulateGas) + .typed(ChainConfigContractProxy) + .init(OptionalValue::>::Some( + SovereignConfig::default_config_for_test(), + )) + .returns(ReturnsNewAddress) + .code(CHAIN_CONFIG_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + template_contracts.push(Bech32Address::from(chain_config_template)); + + let esdt_safe_template = self + .interactor() + .tx() + .from(caller.clone()) + .gas(SimulateGas) + .typed(MvxEsdtSafeProxy) + .init( + Bech32Address::from(caller.clone()), + forge_address, + chain_id, + OptionalValue::>::None, + ) + .returns(ReturnsNewAddress) + .code(MVX_ESDT_SAFE_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + template_contracts.push(Bech32Address::from(esdt_safe_template.clone())); + + let fee_market_address = self + .interactor() + .tx() + .from(caller.clone()) + .gas(SimulateGas) + .typed(MvxFeeMarketProxy) + .init( + Bech32Address::from(esdt_safe_template), + None::>, + ) + .returns(ReturnsNewAddress) + .code(FEE_MARKET_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + template_contracts.push(Bech32Address::from(fee_market_address)); + + let header_verifier_address = self + .interactor() + .tx() + .from(caller.clone()) + .gas(SimulateGas) + .typed(HeaderverifierProxy) + .init(MultiValueEncoded::new()) + .returns(ReturnsNewAddress) + .code(HEADER_VERIFIER_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + template_contracts.push(Bech32Address::from(header_verifier_address)); + + template_contracts + } + + async fn deploy_header_verifier( + &mut self, + caller: Address, + chain_id: String, + contracts_array: Vec>, + ) { + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(50_000_000u64) + .typed(HeaderverifierProxy) + .init(MultiValueEncoded::from_iter(contracts_array)) + .returns(ReturnsNewAddress) + .code(HEADER_VERIFIER_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state() + .set_header_verifier_address(AddressInfo { + address: new_address_bech32, + chain_id, + }); + } + + async fn deploy_mvx_esdt_safe( + &mut self, + caller: Address, + forge_address: &Address, + chain_id: String, + opt_config: OptionalValue>, + ) { + let owner_address = caller.clone(); + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(100_000_000u64) + .typed(MvxEsdtSafeProxy) + .init( + owner_address, + forge_address, + SOVEREIGN_TOKEN_PREFIX, + opt_config, + ) + .returns(ReturnsNewAddress) + .code(MVX_ESDT_SAFE_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state() + .set_mvx_esdt_safe_contract_address(AddressInfo { + address: new_address_bech32.clone(), + chain_id, + }); + } + + async fn register_as_validator( + &mut self, + shard: u32, + payment: MultiEgldOrEsdtPayment, + chain_config_address: Bech32Address, + ) { + let bridge_owner = self.get_bridge_owner_for_shard(shard).clone(); + + let mut secret_key = SecretKey::default(); + secret_key.set_by_csprng(); + + let secret_key_bytes = secret_key + .serialize() + .expect("Failed to serialize BLS secret key"); + let public_key_bytes = secret_key + .get_public_key() + .serialize() + .expect("Failed to serialize BLS public key"); + + self.common_state() + .add_bls_secret_key(shard, secret_key_bytes); + + let bls_key_buffer = ManagedBuffer::::new_from_bytes(&public_key_bytes); + + self.interactor() + .tx() + .from(bridge_owner) + .to(chain_config_address) + .gas(SimulateGas) + .typed(ChainConfigContractProxy) + .register(bls_key_buffer) + .payment(payment) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn deploy_fee_market( + &mut self, + caller: Address, + chain_id: String, + esdt_safe_address: Bech32Address, + fee: OptionalValue>, + ) { + let fee = fee.into_option(); + + let new_address = self + .interactor() + .tx() + .from(caller) + .gas(80_000_000u64) + .typed(MvxFeeMarketProxy) + .init(esdt_safe_address, fee) + .returns(ReturnsNewAddress) + .code(FEE_MARKET_CODE_PATH) + .code_metadata(metadata()) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + self.common_state().set_fee_market_address(AddressInfo { + address: new_address_bech32.clone(), + chain_id, + }); + } + + async fn deploy_testing_sc(&mut self) { + let bridge_owner = self.get_bridge_owner_for_shard(SHARD_0).clone(); + let new_address = self + .interactor() + .tx() + .from(bridge_owner) + .gas(SimulateGas) + .typed(TestingScProxy) + .init() + .code(TESTING_SC_CODE_PATH) + .code_metadata(metadata()) + .returns(ReturnsNewAddress) + .run() + .await; + + let new_address_bech32 = Bech32Address::from(&new_address); + + self.common_state() + .set_testing_sc_address(new_address_bech32.clone()); + + println!("new testing sc address: {new_address_bech32}"); + } + + async fn deploy_and_complete_setup_phase( + &mut self, + deploy_cost: OptionalValue>, + optional_sov_config: OptionalValue>, + optional_esdt_safe_config: OptionalValue>, + ) { + let fee_struct = self.create_standard_fee(); + self.deploy_and_setup_common( + deploy_cost.clone(), + optional_sov_config, + optional_esdt_safe_config, + OptionalValue::Some(fee_struct), + ) + .await; + let fee_token_id = self.state().get_fee_token_id(); + let fee_token_fee_market = self.create_serializable_token(fee_token_id, 0u64); + self.common_state() + .set_fee_market_token_for_all_shards(fee_token_fee_market); + self.common_state().set_fee_status_for_all_shards(true); + self.common_state() + .set_mvx_egld_balance_for_all_shards(0u64); + } + + async fn unpause_forge(&mut self, sovereign_forge_address: Address) { + let bridge_owner = self.get_bridge_owner_for_shard(SHARD_0).clone(); + + self.interactor() + .tx() + .from(bridge_owner) + .to(sovereign_forge_address) + .typed(SovereignForgeProxy) + .unpause_endpoint() + .gas(SimulateGas) + .run() + .await; + } + + async fn deploy_and_setup_common( + &mut self, + deploy_cost: OptionalValue>, + optional_sov_config: OptionalValue>, + optional_esdt_safe_config: OptionalValue>, + fee: OptionalValue>, + ) { + let initial_caller = self.get_bridge_owner_for_shard(SHARD_0).clone(); + + let sovereign_forge_address = self + .deploy_sovereign_forge( + initial_caller.clone(), + OptionalValue::Some(&BigUint::from(DEPLOY_COST)), + ) + .await; + self.unpause_forge(sovereign_forge_address.clone()).await; + + let trusted_token = self.common_state().get_trusted_token(); + + self.register_trusted_token(initial_caller.clone(), trusted_token.as_str()) + .await; + + for shard_id in 0..NUMBER_OF_SHARDS { + let caller = self.get_bridge_owner_for_shard(shard_id); + let template_contracts = self + .deploy_template_contracts(caller.clone(), &sovereign_forge_address, CHAIN_ID) + .await; + + let ( + chain_config_address, + mvx_esdt_safe_address, + fee_market_address, + header_verifier_address, + ) = match template_contracts.as_slice() { + [a, b, c, d] => (a.clone(), b.clone(), c.clone(), d.clone()), + _ => panic!( + "Expected 4 deployed contract addresses, got {}", + template_contracts.len() + ), + }; + + self.finish_init_setup_phase_for_one_shard( + shard_id, + initial_caller.clone(), + sovereign_forge_address.clone(), + TemplateAddresses { + chain_config_address: chain_config_address.clone(), + header_verifier_address: header_verifier_address.clone(), + esdt_safe_address: mvx_esdt_safe_address.clone(), + fee_market_address: fee_market_address.clone(), + }, + ) + .await; + println!("Finished setup phase for shard {shard_id}"); + } + + for shard in 0..NUMBER_OF_SHARDS { + self.deploy_on_one_shard( + shard, + deploy_cost.clone(), + optional_esdt_safe_config.clone(), + optional_sov_config.clone(), + fee.clone(), + ) + .await; + } + + self.deploy_testing_sc().await; + } + + async fn finish_init_setup_phase_for_one_shard( + &mut self, + shard_id: u32, + initial_caller: Address, + sovereign_forge_address: Address, + template_addresses: TemplateAddresses, + ) { + let caller = self.get_bridge_owner_for_shard(shard_id); + + self.deploy_chain_factory( + caller.clone(), + sovereign_forge_address.clone(), + template_addresses.clone(), + ) + .await; + self.register_chain_factory(initial_caller.clone(), shard_id) + .await; + } + + async fn register_trusted_token(&mut self, caller: Address, trusted_token: &str) { + let forge_address = &self + .common_state() + .sovereign_forge_sc_address + .clone() + .unwrap(); + + self.interactor() + .tx() + .from(caller) + .to(forge_address) + .typed(SovereignForgeProxy) + .register_trusted_token(ManagedBuffer::from(trusted_token)) + .gas(SimulateGas) + .run() + .await; + } + + async fn deploy_on_one_shard( + &mut self, + shard: u32, + deploy_cost: OptionalValue>, + optional_esdt_safe_config: OptionalValue>, + optional_sov_config: OptionalValue>, + fee: OptionalValue>, + ) { + let caller = self.get_sovereign_owner_for_shard(shard); + let preferred_chain_id = Self::generate_random_chain_id(); + self.common_state().add_chain_id(preferred_chain_id.clone()); + self.deploy_phase_one( + caller.clone(), + deploy_cost.clone(), + Some(preferred_chain_id.clone().into()), + optional_sov_config.clone(), + ) + .await; + let chain_config_address = self.get_chain_config_address(&preferred_chain_id).await; + self.register_as_validator( + shard, + MultiEgldOrEsdtPayment::new(), + chain_config_address.clone(), + ) + .await; + + self.deploy_phase_two(optional_esdt_safe_config.clone(), caller.clone()) + .await; + self.register_native_token(caller.clone(), &preferred_chain_id) + .await; + + let mvx_esdt_safe_address = self + .get_sc_address_from_sovereign_forge(preferred_chain_id.as_str(), ScArray::ESDTSafe) + .await; + + self.set_special_roles_for_trusted_token(mvx_esdt_safe_address.clone()) + .await; + + self.deploy_phase_three(caller.clone(), fee.clone()).await; + self.deploy_phase_four(caller.clone()).await; + + self.complete_setup_phase(caller.clone()).await; + self.check_setup_phase_status(&preferred_chain_id, true) + .await; + + self.update_smart_contracts_addresses_in_state(preferred_chain_id.clone()) + .await; + + println!("Finished deployment for shard {shard}"); + } + + async fn register_native_token(&mut self, caller: Address, chain_id: &str) { + let mvx_esdt_safe_address = self + .get_sc_address_from_sovereign_forge(chain_id, ScArray::ESDTSafe) + .await; + + self.interactor() + .tx() + .from(caller) + .to(mvx_esdt_safe_address) + .gas(90_000_000u64) + .typed(MvxEsdtSafeProxy) + .register_native_token( + ManagedBuffer::from(NATIVE_TOKEN_TICKER), + ManagedBuffer::from(NATIVE_TOKEN_NAME), + ) + .egld(BigUint::from(ISSUE_COST)) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn get_sc_address_from_sovereign_forge( + &mut self, + chain_id: &str, + sc_id: ScArray, + ) -> Address { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + self.interactor() + .query() + .to(sovereign_forge_address) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(chain_id) + .returns(ReturnsResult) + .run() + .await + .into_iter() + .find(|sc| sc.id == sc_id) + .unwrap() + .address + .to_address() + } + + async fn register_chain_factory(&mut self, caller: Address, shard_id: u32) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + let chain_factory_address = self + .common_state() + .get_chain_factory_sc_address(shard_id) + .clone(); + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(30_000_000u64) + .typed(SovereignForgeProxy) + .register_chain_factory(shard_id, chain_factory_address) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn update_smart_contracts_addresses_in_state(&mut self, chain_id: String) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + let result_value = self + .interactor() + .query() + .to(sovereign_forge_address) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(chain_id.clone()) + .returns(ReturnsResult) + .run() + .await; + + for contract in result_value { + let address = Bech32Address::from(contract.address.to_address()); + match contract.id { + ScArray::ChainConfig => { + self.common_state() + .set_chain_config_sc_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); + } + ScArray::ESDTSafe => { + self.common_state() + .set_mvx_esdt_safe_contract_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); + } + ScArray::FeeMarket => { + self.common_state().set_fee_market_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); + } + ScArray::HeaderVerifier => { + self.common_state() + .set_header_verifier_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); + } + _ => {} + } + } + } + + async fn get_chain_config_address(&mut self, chain_id: &str) -> Bech32Address { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + let result_value = self + .interactor() + .query() + .to(sovereign_forge_address) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(chain_id) + .returns(ReturnsResult) + .run() + .await; + + for contract in result_value { + if let ScArray::ChainConfig = contract.id { + return Bech32Address::from(contract.address.to_address()); + } + } + + panic!("Chain config address not found for chain_id: {}", chain_id); + } + + async fn deploy_phase_one( + &mut self, + caller: Address, + opt_egld_amount: OptionalValue>, + opt_preferred_chain_id: Option>, + opt_config: OptionalValue>, + ) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + let mut egld_amount = BigUint::default(); + + if opt_egld_amount.is_some() { + egld_amount = opt_egld_amount.into_option().unwrap(); + } + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(30_000_000u64) + .typed(SovereignForgeProxy) + .deploy_phase_one(opt_preferred_chain_id, opt_config) + .egld(egld_amount) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn deploy_phase_two( + &mut self, + opt_config: OptionalValue>, + caller: Address, + ) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(30_000_000u64) + .typed(SovereignForgeProxy) + .deploy_phase_two(opt_config) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn deploy_phase_three( + &mut self, + caller: Address, + fee: OptionalValue>, + ) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(30_000_000u64) + .typed(SovereignForgeProxy) + .deploy_phase_three(fee) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn deploy_phase_four(&mut self, caller: Address) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(30_000_000u64) + .typed(SovereignForgeProxy) + .deploy_phase_four() + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn complete_setup_phase(&mut self, caller: Address) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(90_000_000u64) + .typed(SovereignForgeProxy) + .complete_setup_phase() + .returns(ReturnsGasUsed) + .run() + .await; + } + + async fn update_esdt_safe_config( + &mut self, + hash_of_hashes: ManagedBuffer, + esdt_safe_config: EsdtSafeConfig, + nonce: TxNonce, + shard: u32, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let current_mvx_esdt_safe_address = + self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + self.interactor() + .tx() + .from(bridge_service) + .to(current_mvx_esdt_safe_address) + .gas(SimulateGas) + .typed(MvxEsdtSafeProxy) + .update_esdt_safe_config( + hash_of_hashes, + UpdateEsdtSafeConfigOperation { + esdt_safe_config, + nonce, + }, + ) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn set_fee( + &mut self, + hash_of_hashes: ManagedBuffer, + fee_operation: SetFeeOperation, + shard: u32, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let current_fee_market_address = self.common_state().get_fee_market_address(shard).clone(); + + self.interactor() + .tx() + .from(bridge_service) + .to(current_fee_market_address) + .gas(SimulateGas) + .typed(MvxFeeMarketProxy) + .set_fee(hash_of_hashes, fee_operation) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn remove_fee( + &mut self, + hash_of_hashes: ManagedBuffer, + fee_operation: RemoveFeeOperation, + shard: u32, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let current_fee_market_address = self.common_state().get_fee_market_address(shard).clone(); + + self.interactor() + .tx() + .from(bridge_service) + .to(current_fee_market_address) + .gas(SimulateGas) + .typed(MvxFeeMarketProxy) + .remove_fee(hash_of_hashes, fee_operation) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn set_token_burn_mechanism( + &mut self, + token_id: EgldOrEsdtTokenIdentifier, + shard: u32, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let current_mvx_esdt_safe_address = + self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + if self.common_state().get_is_burn_mechanism_set() { + return; + } + + let token_burn_mechanism_operation = SetBurnMechanismOperation { + token_id, + nonce: self + .common_state() + .get_and_increment_operation_nonce(¤t_mvx_esdt_safe_address.to_string()), + }; + + let token_burn_mechanism_operation_hash = token_burn_mechanism_operation.generate_hash(); + let token_burn_mechanism_hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&token_burn_mechanism_operation_hash.to_vec())); + + self.register_operation( + shard, + &token_burn_mechanism_hash_of_hashes, + MultiValueEncoded::from(ManagedVec::from(vec![token_burn_mechanism_operation_hash])), + ) + .await; + + self.interactor() + .tx() + .from(bridge_service) + .to(current_mvx_esdt_safe_address) + .gas(SimulateGas) + .typed(MvxEsdtSafeProxy) + .set_token_burn_mechanism( + token_burn_mechanism_hash_of_hashes, + token_burn_mechanism_operation, + ) + .returns(ReturnsResultUnmanaged) + .run() + .await; + + self.common_state().set_is_burn_mechanism_set(true); + } + + async fn set_token_lock_mechanism( + &mut self, + token_id: EgldOrEsdtTokenIdentifier, + shard: u32, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let current_mvx_esdt_safe_address = + self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + let token_lock_mechanism_operation = SetLockMechanismOperation { + token_id, + nonce: self + .common_state() + .get_and_increment_operation_nonce(¤t_mvx_esdt_safe_address.to_string()), + }; + + let token_lock_mechanism_operation_hash = token_lock_mechanism_operation.generate_hash(); + let token_lock_mechanism_hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&token_lock_mechanism_operation_hash.to_vec())); + + self.register_operation( + shard, + &token_lock_mechanism_hash_of_hashes, + MultiValueEncoded::from(ManagedVec::from(vec![token_lock_mechanism_operation_hash])), + ) + .await; + + self.interactor() + .tx() + .from(bridge_service) + .to(current_mvx_esdt_safe_address) + .gas(SimulateGas) + .typed(MvxEsdtSafeProxy) + .set_token_lock_mechanism( + token_lock_mechanism_hash_of_hashes, + token_lock_mechanism_operation, + ) + .returns(ReturnsResultUnmanaged) + .run() + .await; + + self.common_state().set_is_burn_mechanism_set(false); + } + + async fn set_token_burn_mechanism_before_setup_phase(&mut self, caller: Address) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + let trusted_token = self.common_state().get_trusted_token(); + + self.interactor() + .tx() + .from(caller) + .to(sovereign_forge_address) + .gas(90_000_000u64) + .typed(SovereignForgeProxy) + .set_token_burn_mechanism(EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str())) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn set_special_roles_for_trusted_token(&mut self, for_address: Address) { + let user_address = self.user_address().clone(); + let trusted_token = self.common_state().get_trusted_token(); + + let roles = vec![EsdtLocalRole::Mint, EsdtLocalRole::Burn]; + + self.interactor() + .tx() + .from(user_address) + .to(ESDTSystemSCAddress) + .gas(SimulateGas) + .typed(ESDTSystemSCProxy) + .set_special_roles( + ManagedAddress::from_address(&for_address), + EsdtTokenIdentifier::from(trusted_token.as_str()), + roles.into_iter(), + ) + .run() + .await; + } + + async fn register_operation( + &mut self, + shard: u32, + hash_of_hashes: &ManagedBuffer, + operations_hashes: MultiValueEncoded>, + ) { + let bridge_service = self.get_bridge_service_for_shard(shard).clone(); + let header_verifier_address = self + .common_state() + .get_header_verifier_address(shard) + .clone(); + + let secret_keys = self + .common_state() + .get_bls_secret_keys(shard) + .cloned() + .unwrap_or_else(|| panic!("No BLS secret keys registered for shard {shard}")); + + let (signature, bitmap) = + Self::create_aggregated_signature_and_bitmap(&secret_keys, hash_of_hashes); + let epoch = 0u32; + + self.interactor() + .tx() + .from(bridge_service) + .to(header_verifier_address) + .gas(SimulateGas) + .typed(HeaderverifierProxy) + .register_bridge_operations( + signature, + hash_of_hashes.clone(), + bitmap, + epoch, + operations_hashes, + ) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + fn create_aggregated_signature_and_bitmap( + secret_keys: &[Vec], + message: &ManagedBuffer, + ) -> (ManagedBuffer, ManagedBuffer) { + assert!( + !secret_keys.is_empty(), + "At least one BLS key is required to build the signature bitmap", + ); + + let message_bytes = message.to_vec(); + let mut signatures = Vec::with_capacity(secret_keys.len()); + + for key_bytes in secret_keys { + let secret_key = SecretKey::from_serialized(key_bytes) + .expect("Failed to deserialize stored BLS secret key"); + let signature = secret_key.sign(&message_bytes); + signatures.push(signature); + } + + let mut aggregated_signature = G1::default(); + aggregated_signature.aggregate(&signatures); + + let signature_bytes = aggregated_signature + .serialize() + .expect("Failed to serialize aggregated BLS signature"); + let bitmap_bytes = Self::build_bitmap(secret_keys.len()); + + ( + ManagedBuffer::new_from_bytes(&signature_bytes), + ManagedBuffer::new_from_bytes(&bitmap_bytes), + ) + } + + fn build_bitmap(num_signers: usize) -> Vec { + assert!(num_signers > 0, "Cannot build bitmap with zero signers"); + + let byte_len = num_signers.div_ceil(8); + let mut bitmap = vec![0u8; byte_len]; + + for signer_index in 0..num_signers { + let byte_index = signer_index / 8; + let bit_index = signer_index % 8; + bitmap[byte_index] |= 1 << bit_index; + } + + bitmap + } + + async fn switch_pause_status(&mut self, status: bool, shard: u32) { + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let bridge_address = self.get_bridge_service_for_shard(shard).clone(); + + let operation = PauseStatusOperation { + status, + nonce: self + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + }; + + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + self.register_operation(shard, &hash_of_hashes, operations_hashes) + .await; + + self.interactor() + .tx() + .from(bridge_address) + .to(mvx_esdt_safe_address.clone()) + .gas(SimulateGas) + .typed(MvxEsdtSafeProxy) + .switch_pause_status(hash_of_hashes, operation) + .run() + .await; + + let current_status = self + .interactor() + .query() + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .paused_status() + .returns(ReturnsResult) + .run() + .await; + + assert_eq!(current_status, status, "Pause status is not correct"); + } + + async fn complete_header_verifier_setup_phase(&mut self, caller: Address, shard: u32) { + let header_verifier_address = self + .common_state() + .get_header_verifier_address(shard) + .clone(); + + self.interactor() + .tx() + .from(caller) + .to(header_verifier_address) + .gas(90_000_000u64) + .typed(HeaderverifierProxy) + .complete_setup_phase() + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn complete_chain_config_setup_phase(&mut self, shard: u32) { + let bridge_owner = self.get_bridge_owner_for_shard(shard).clone(); + let chain_config_address = self + .common_state() + .current_chain_config_sc_address() + .clone(); + + self.interactor() + .tx() + .from(bridge_owner) + .to(chain_config_address) + .gas(SimulateGas) + .typed(HeaderverifierProxy) + .complete_setup_phase() + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn deposit_in_mvx_esdt_safe( + &mut self, + to: Address, + shard: u32, + opt_transfer_data: OptionalValueTransferDataTuple, + payments: PaymentsVec, + expected_error_message: Option<&str>, + expected_log: Option>>, + ) { + let user_address = self.user_address().clone(); + let current_mvx_esdt_safe_address = + self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let (response, logs) = self + .interactor() + .tx() + .from(user_address) + .to(current_mvx_esdt_safe_address) + .gas(90_000_000u64) + .typed(MvxEsdtSafeProxy) + .deposit(to, opt_transfer_data) + .payment(payments) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run() + .await; + + self.assert_expected_error_message(response, expected_error_message); + + assert_expected_logs(logs, expected_log.unwrap_or_default()); + } + + async fn withdraw_from_testing_sc( + &mut self, + expected_token: EsdtTokenInfo, + nonce: TxNonce, + amount: BigUint, + ) { + let user_address = self.user_address().clone(); + let testing_sc_address = self.common_state().current_testing_sc_address().clone(); + self.interactor() + .tx() + .from(user_address) + .to(testing_sc_address) + .gas(90_000_000u64) + .typed(TestingScProxy) + .send_tokens(expected_token.token_id, nonce, amount.clone()) + .returns(ReturnsResultUnmanaged) + .run() + .await; + } + + async fn execute_operations_in_mvx_esdt_safe( + &mut self, + caller: Address, + shard: u32, + hash_of_hashes: ManagedBuffer, + operation: Operation, + expected_logs: Vec>, + ) { + let current_mvx_esdt_safe_address = + self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let (response, logs) = self + .interactor() + .tx() + .from(caller) + .to(current_mvx_esdt_safe_address) + .gas(130_000_000u64) + .typed(MvxEsdtSafeProxy) + .execute_operations(hash_of_hashes, operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run() + .await; + + self.assert_expected_error_message(response, None); + + assert_expected_logs(logs, expected_logs); + } + + async fn register_token( + &mut self, + shard: u32, + token: RegisterTokenOperation, + ) -> String { + let user_address = self.user_address().clone(); + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let token_hash = token.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&token_hash.to_vec())); + + let (response, token_id) = self + .interactor() + .tx() + .from(user_address) + .to(mvx_esdt_safe_address) + .gas(90_000_000u64) + .typed(MvxEsdtSafeProxy) + .register_sovereign_token(hash_of_hashes, token) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsNewTokenIdentifier) + .run() + .await; + + self.assert_expected_error_message(response, None); + + token_id + } + + async fn get_sov_to_mvx_token_id( + &mut self, + shard: u32, + token_id: EgldOrEsdtTokenIdentifier, + ) -> EgldOrEsdtTokenIdentifier { + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let user_address = self.user_address().clone(); + self.interactor() + .tx() + .from(user_address) + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .sovereign_to_multiversx_token_id_mapper(token_id) + .returns(ReturnsResult) + .run() + .await + } + + async fn get_sov_to_mvx_token_id_with_nonce( + &mut self, + shard: u32, + token_id: EgldOrEsdtTokenIdentifier, + nonce: u64, + ) -> EsdtInfo { + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let user_address = self.user_address().clone(); + self.interactor() + .tx() + .from(user_address) + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .sovereign_to_multiversx_esdt_info_mapper(token_id, nonce) + .returns(ReturnsResult) + .run() + .await + } + + async fn check_setup_phase_status(&mut self, chain_id: &str, expected_value: bool) { + let sovereign_forge_address = self + .common_state() + .current_sovereign_forge_sc_address() + .clone(); + let result_value = self + .interactor() + .query() + .to(sovereign_forge_address) + .typed(SovereignForgeProxy) + .sovereign_setup_phase(chain_id) + .returns(ReturnsResultUnmanaged) + .run() + .await; + + assert_eq!( + result_value, expected_value, + "Expected setup phase status to be {expected_value}, but got {result_value}" + ); + } + + async fn check_registered_operation_status( + &mut self, + shard_id: u32, + hash_of_hashes: &ManagedBuffer, + operation_hash: ManagedBuffer, + expected_value: OperationHashStatus, + ) { + let header_verifier_address = self + .common_state() + .get_header_verifier_address(shard_id) + .clone(); + let response = self + .interactor() + .query() + .to(header_verifier_address) + .typed(HeaderverifierProxy) + .operation_hash_status(hash_of_hashes, operation_hash) + .returns(ReturnsResult) + .run() + .await; + + assert_eq!( + response, expected_value, + "Expected operation hash status {:?} does not match with the actual value {:?}", + expected_value, response + ); + } + + async fn get_mapped_token( + &mut self, + config: ActionConfig, + original_token: &EsdtTokenInfo, + amount: &BigUint, + ) -> EsdtTokenInfo { + let edge_case = original_token.token_type == EsdtTokenType::Fungible + || (self.is_nft(original_token) && config.expected_log_error.is_some()); + + let (mapped_token_id, mapped_nonce) = if edge_case { + let token_id = self + .get_sov_to_mvx_token_id(config.shard, original_token.clone().token_id) + .await; + (token_id, original_token.nonce) + } else { + let token_info = self + .get_sov_to_mvx_token_id_with_nonce( + config.shard, + original_token.clone().token_id, + original_token.nonce, + ) + .await; + (token_info.token_identifier, token_info.token_nonce) + }; + + EsdtTokenInfo { + token_id: mapped_token_id, + nonce: mapped_nonce, + token_type: original_token.token_type, + decimals: original_token.decimals, + amount: amount.clone(), + } + } + + async fn retrieve_current_fee_token_for_wallet(&mut self) -> EsdtTokenInfo { + let fee_token_id = self + .common_state() + .fee_market_tokens + .get("0") + .map(|t| t.token_id.clone()) + .expect("Fee market token for shard 0 not found"); + + let user_address = &self.user_address().clone(); + let balances = self.interactor().get_account_esdt(user_address).await; + + let amount = if let Some(esdt_balance) = balances.get(&fee_token_id) { + BigUint::from( + num_bigint::BigUint::parse_bytes(esdt_balance.balance.as_bytes(), 10) + .expect("Failed to parse fee token balance as number"), + ) + } else { + BigUint::zero() + }; + + EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(fee_token_id.as_str()), + nonce: 0, + token_type: EsdtTokenType::Fungible, + amount, + decimals: 18, + } + } + + async fn retrieve_current_trusted_token_for_wallet(&mut self) -> EsdtTokenInfo { + let user_address = &self.user_address().clone(); + let balances = self.interactor().get_account_esdt(user_address).await; + let trusted_token = self.common_state().get_trusted_token(); + + let amount = if let Some(esdt_balance) = balances.get(trusted_token.as_str()) { + BigUint::from( + num_bigint::BigUint::parse_bytes(esdt_balance.balance.as_bytes(), 10) + .expect("Failed to parse fee token balance as number"), + ) + } else { + BigUint::zero() + }; + + EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()), + nonce: 0, + token_type: EsdtTokenType::Fungible, + amount, + decimals: 18, + } + } + + async fn remove_fee_wrapper(&mut self, shard: u32) { + let fee_activated = self.common_state().get_fee_status_for_shard(shard); + + if !fee_activated { + return; + } + + let fee_token = self.state().get_fee_token_identifier(); + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + let operation: RemoveFeeOperation = RemoveFeeOperation { + token_id: fee_token.clone(), + nonce: self + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + }; + + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let operations_hashes = MultiValueEncoded::from_iter(vec![operation_hash.clone()]); + + self.register_operation(shard, &hash_of_hashes, operations_hashes) + .await; + + self.remove_fee(hash_of_hashes, operation, shard).await; + self.common_state().set_fee_status_for_shard(shard, false); + } + + async fn set_fee_wrapper(&mut self, fee: FeeStruct, shard: u32) { + let fee_activated = self.common_state().get_fee_status_for_shard(shard); + + if fee_activated { + return; + } + + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let operation: SetFeeOperation = SetFeeOperation { + fee_struct: fee.clone(), + nonce: self + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + }; + + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let operations_hashes = MultiValueEncoded::from_iter(vec![operation_hash.clone()]); + + self.register_operation(shard, &hash_of_hashes, operations_hashes) + .await; + + self.set_fee(hash_of_hashes, operation, shard).await; + self.common_state().set_fee_status_for_shard(shard, true); + } + + async fn get_native_token(&mut self, shard: u32) -> EgldOrEsdtTokenIdentifier { + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + self.interactor() + .query() + .to(mvx_esdt_safe_address) + .typed(MvxEsdtSafeProxy) + .native_token() + .returns(ReturnsResult) + .run() + .await + } +} diff --git a/common/common-interactor/src/interactor_common_state.rs b/common/common-interactor/src/interactor_common_state.rs new file mode 100644 index 000000000..33b634dca --- /dev/null +++ b/common/common-interactor/src/interactor_common_state.rs @@ -0,0 +1,364 @@ +use std::{ + collections::HashMap, + io::{Read, Write}, + path::Path, + str::FromStr, +}; + +use common_test_setup::constants::STATE_FILE; +use error_messages::{ + NO_KNOWN_CHAIN_CONFIG_SC, NO_KNOWN_CHAIN_FACTORY_SC, NO_KNOWN_FEE_MARKET, NO_KNOWN_FEE_TOKEN, + NO_KNOWN_HEADER_VERIFIER, NO_KNOWN_MVX_ESDT_SAFE, NO_KNOWN_SOVEREIGN_FORGE_SC, + NO_KNOWN_TESTING_SC, NO_KNOWN_TRUSTED_TOKEN, +}; +use multiversx_sc::{ + codec::num_bigint, + imports::Bech32Address, + types::{BigUint, EgldOrEsdtTokenIdentifier, EsdtTokenType}, +}; +use multiversx_sc_snippets::imports::StaticApi; +use serde::{Deserialize, Serialize}; + +use crate::{ + interactor_state::{AddressInfo, EsdtTokenInfo, ShardAddresses}, + interactor_structs::SerializableToken, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct CommonState { + pub mvx_esdt_safe_addresses: Option, + pub header_verifier_addresses: Option, + pub fee_market_addresses: Option, + pub chain_config_sc_addresses: Option, + pub testing_sc_address: Option, + pub sovereign_forge_sc_address: Option, + pub chain_factory_sc_addresses: Option>, + pub fee_market_tokens: HashMap, + pub trusted_token: Option, + pub fee_status: HashMap, + pub operation_nonce: HashMap, + pub chain_ids: Vec, + pub mvx_egld_balances: Vec<(String, u64)>, + pub testing_egld_balance: u64, + pub bls_secret_keys: HashMap>>, + pub deposited_amount: String, + pub is_burn_mechanism_set: bool, +} + +impl CommonState { + 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_mvx_esdt_safe_contract_address(&mut self, address: AddressInfo) { + let list = self.mvx_esdt_safe_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_header_verifier_address(&mut self, address: AddressInfo) { + let list = self.header_verifier_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_fee_market_address(&mut self, address: AddressInfo) { + let list = self.fee_market_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_chain_config_sc_address(&mut self, address: AddressInfo) { + let list = self.chain_config_sc_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_testing_sc_address(&mut self, address: Bech32Address) { + self.testing_sc_address = Some(address); + } + + pub fn set_sovereign_forge_sc_address(&mut self, address: Bech32Address) { + self.sovereign_forge_sc_address = Some(address); + } + + pub fn set_chain_factory_sc_address(&mut self, address: Bech32Address) { + let list = self.chain_factory_sc_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_fee_status_for_shard(&mut self, shard: u32, status: bool) { + self.fee_status.insert(shard.to_string(), status); + } + + pub fn set_fee_status_for_all_shards(&mut self, status: bool) { + for shard in 0..3 { + self.fee_status.insert(shard.to_string(), status); + } + } + + pub fn set_fee_market_token_for_all_shards(&mut self, token: SerializableToken) { + for shard in 0..3 { + self.fee_market_tokens + .insert(shard.to_string(), token.clone()); + } + } + + pub fn set_fee_market_token_for_shard(&mut self, shard: u32, token: SerializableToken) { + self.fee_market_tokens.insert(shard.to_string(), token); + } + + pub fn add_chain_id(&mut self, chain_id: String) { + self.chain_ids.push(chain_id); + } + + pub fn set_mvx_egld_balance_for_all_shards(&mut self, balance: u64) { + for shard in 0..3 { + self.mvx_egld_balances.push((shard.to_string(), balance)); + } + } + + pub fn set_trusted_token(&mut self, token: String) { + self.trusted_token = Some(token); + } + + pub fn update_mvx_egld_balance_with_amount(&mut self, shard: u32, amount: u64) { + let shard_str = shard.to_string(); + if let Some((_, current_balance)) = self + .mvx_egld_balances + .iter_mut() + .find(|(s, _)| s == &shard_str) + { + *current_balance += amount; + } + } + + pub fn update_testing_egld_balance_with_amount(&mut self, amount: u64) { + self.testing_egld_balance += amount; + } + + fn parse_deposited_amount(&self) -> num_bigint::BigUint { + if self.deposited_amount.is_empty() { + return num_bigint::BigUint::from(0u64); + } + + let trimmed = self.deposited_amount.trim(); + num_bigint::BigUint::from_str(trimmed).unwrap_or_else(|_| { + eprintln!("Failed to parse deposited_amount '{}'", trimmed); + num_bigint::BigUint::from(0u64) + }) + } + + fn biguint_to_num_biguint(amount: &BigUint) -> num_bigint::BigUint { + let amount_bytes = amount.to_bytes_be(); + num_bigint::BigUint::from_bytes_be(amount_bytes.as_slice()) + } + + fn num_biguint_to_biguint(num: &num_bigint::BigUint) -> BigUint { + let bytes = num.to_bytes_be(); + BigUint::from_bytes_be(&bytes) + } + + pub fn add_to_deposited_amount(&mut self, amount: BigUint) { + let current = self.parse_deposited_amount(); + let amount_biguint = Self::biguint_to_num_biguint(&amount); + let sum = current + amount_biguint; + + self.deposited_amount = sum.to_string(); + } + + pub fn subtract_from_deposited_amount(&mut self, amount: BigUint) { + let current = self.parse_deposited_amount(); + let amount_biguint = Self::biguint_to_num_biguint(&amount); + let result = if current >= amount_biguint { + current - amount_biguint + } else { + num_bigint::BigUint::from(0u64) + }; + + self.deposited_amount = result.to_string(); + } + + pub fn set_is_burn_mechanism_set(&mut self, is_burn_mechanism_set: bool) { + self.is_burn_mechanism_set = is_burn_mechanism_set; + } + + pub fn get_is_burn_mechanism_set(&self) -> bool { + self.is_burn_mechanism_set + } + + pub fn get_deposited_amount(&self) -> BigUint { + let num_biguint = self.parse_deposited_amount(); + Self::num_biguint_to_biguint(&num_biguint) + } + + pub fn get_and_increment_operation_nonce(&mut self, contract_address: &str) -> u64 { + let nonce = self.get_operation_nonce(contract_address); + self.increment_operation_nonce(contract_address); + nonce + } + + /// Returns the contract addresses + pub fn current_chain_config_sc_address(&self) -> &Bech32Address { + self.chain_config_sc_addresses + .as_ref() + .expect(NO_KNOWN_CHAIN_CONFIG_SC) + .first() + } + + pub fn current_testing_sc_address(&self) -> &Bech32Address { + self.testing_sc_address.as_ref().expect(NO_KNOWN_TESTING_SC) + } + + pub fn current_sovereign_forge_sc_address(&self) -> &Bech32Address { + self.sovereign_forge_sc_address + .as_ref() + .expect(NO_KNOWN_SOVEREIGN_FORGE_SC) + } + + pub fn get_chain_factory_sc_address(&self, shard: u32) -> &Bech32Address { + self.chain_factory_sc_addresses + .as_ref() + .expect(NO_KNOWN_CHAIN_FACTORY_SC) + .get(shard as usize) + .unwrap_or_else(|| panic!("No Chain Factory SC address for shard {}", shard)) + } + + pub fn get_mvx_esdt_safe_address(&self, shard: u32) -> &Bech32Address { + self.mvx_esdt_safe_addresses + .as_ref() + .expect(NO_KNOWN_MVX_ESDT_SAFE) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No MVX ESDT Safe address for shard {}", shard)) + } + + pub fn get_fee_market_address(&self, shard: u32) -> &Bech32Address { + self.fee_market_addresses + .as_ref() + .expect(NO_KNOWN_FEE_MARKET) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No Fee Market address for shard {}", shard)) + } + + pub fn get_header_verifier_address(&self, shard: u32) -> &Bech32Address { + self.header_verifier_addresses + .as_ref() + .expect(NO_KNOWN_HEADER_VERIFIER) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No Header Verifier address for shard {}", shard)) + } + + pub fn get_fee_status_for_shard(&self, shard: u32) -> bool { + self.fee_status + .get(&shard.to_string()) + .cloned() + .unwrap_or(false) + } + + pub fn get_fee_market_token_amount_for_shard(&self, shard: u32) -> u64 { + self.fee_market_tokens + .get(&shard.to_string()) + .cloned() + .expect(NO_KNOWN_FEE_TOKEN) + .amount + } + + pub fn get_fee_market_token_for_shard_converted(&self, shard: u32) -> EsdtTokenInfo { + let token = self + .fee_market_tokens + .get(&shard.to_string()) + .cloned() + .expect(NO_KNOWN_FEE_TOKEN); + EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(token.token_id.as_str()), + nonce: token.nonce, + token_type: EsdtTokenType::from(token.token_type), + decimals: token.decimals, + amount: BigUint::from(token.amount), + } + } + + pub fn get_fee_market_token_for_shard(&self, shard: u32) -> SerializableToken { + self.fee_market_tokens + .get(&shard.to_string()) + .cloned() + .expect(NO_KNOWN_FEE_TOKEN) + } + + pub fn get_chain_id_for_shard(&self, shard: u32) -> &String { + self.chain_ids + .get(shard as usize) + .unwrap_or_else(|| panic!("No chain ID for shard {}", shard)) + } + + pub fn get_mvx_egld_balance_for_shard(&self, shard: u32) -> u64 { + self.mvx_egld_balances + .get(shard as usize) + .map(|(_, balance)| *balance) + .unwrap_or(0u64) + } + + pub fn get_testing_egld_balance(&self) -> u64 { + self.testing_egld_balance + } + + pub fn get_trusted_token(&self) -> String { + self.trusted_token + .as_ref() + .expect(NO_KNOWN_TRUSTED_TOKEN) + .clone() + } + + pub fn add_bls_secret_key(&mut self, shard: u32, secret_key_bytes: Vec) { + let shard_key = shard.to_string(); + self.bls_secret_keys + .entry(shard_key) + .or_default() + .push(secret_key_bytes); + } + + pub fn get_bls_secret_keys(&self, shard: u32) -> Option<&Vec>> { + let shard_key = shard.to_string(); + self.bls_secret_keys.get(&shard_key) + } + + pub fn get_operation_nonce(&self, contract_address: &str) -> u64 { + self.operation_nonce + .get(contract_address) + .copied() + .unwrap_or(0) + } + + pub fn increment_operation_nonce(&mut self, contract_address: &str) { + let current_nonce = self + .operation_nonce + .entry(contract_address.to_string()) + .or_insert(0); + *current_nonce += 1; + } + + pub fn set_operation_nonce(&mut self, contract_address: &str, nonce: u64) { + self.operation_nonce + .entry(contract_address.to_string()) + .and_modify(|existing| *existing = nonce) + .or_insert(nonce); + } +} + +impl Drop for CommonState { + // 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/interactor/src/config.rs b/common/common-interactor/src/interactor_config.rs similarity index 89% rename from interactor/src/config.rs rename to common/common-interactor/src/interactor_config.rs index 2c3692cbb..19cca077c 100644 --- a/interactor/src/config.rs +++ b/common/common-interactor/src/interactor_config.rs @@ -6,7 +6,7 @@ use std::io::Read; /// Config file const CONFIG_FILE: &str = "config.toml"; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "lowercase")] pub enum ChainType { Real, @@ -14,7 +14,7 @@ pub enum ChainType { } /// Contract Interact configuration -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct Config { pub gateway_uri: String, pub chain_type: ChainType, @@ -22,8 +22,7 @@ pub struct Config { impl Config { // Deserializes config from file - #[allow(clippy::new_without_default)] - pub fn new() -> Self { + 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(); diff --git a/common/common-interactor/src/interactor_helpers.rs b/common/common-interactor/src/interactor_helpers.rs new file mode 100644 index 000000000..612acf6aa --- /dev/null +++ b/common/common-interactor/src/interactor_helpers.rs @@ -0,0 +1,902 @@ +use std::path::Path; + +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::constants::{ + DEPOSIT_EVENT, FEE_MARKET_SHARD_0, FEE_MARKET_SHARD_1, FEE_MARKET_SHARD_2, GAS_LIMIT, + MVX_ESDT_SAFE_SHARD_0, MVX_ESDT_SAFE_SHARD_1, MVX_ESDT_SAFE_SHARD_2, PER_GAS, PER_TRANSFER, + SC_CALL_EVENT, SHARD_1, TESTING_SC, TESTING_SC_ENDPOINT, TRUSTED_TOKEN_NAME, + UNKNOWN_FEE_MARKET, UNKNOWN_MVX_ESDT_SAFE, USER_ADDRESS_STR, +}; +use common_test_setup::log; +use error_messages::{AMOUNT_IS_TOO_LARGE, FAILED_TO_PARSE_AS_NUMBER}; +use multiversx_sc::api::{ESDT_LOCAL_BURN_FUNC_NAME, ESDT_NFT_BURN_FUNC_NAME}; +use multiversx_sc::{ + codec::{num_bigint, TopEncode}, + imports::{Bech32Address, MultiValue3, OptionalValue}, + types::{ + Address, BigUint, EgldOrEsdtTokenPayment, EsdtTokenData, EsdtTokenType, ManagedAddress, + ManagedBuffer, ManagedVec, MultiValueEncoded, + }, +}; +use multiversx_sc_snippets::{ + hex, + imports::{StaticApi, Wallet}, + multiversx_sc_scenario::{ + multiversx_chain_vm::crypto_functions::sha256, scenario_model::TxResponseStatus, + }, + test_wallets, Interactor, +}; +use rand::{distr::Alphanumeric, Rng}; +use structs::{ + aliases::PaymentsVec, + fee::{FeeStruct, FeeType}, + operation::{Operation, OperationData, OperationEsdtPayment, TransferData}, +}; + +use crate::{ + interactor_common_state::CommonState, + interactor_state::{EsdtTokenInfo, State}, + interactor_structs::{ActionConfig, BalanceCheckConfig, SerializableToken}, +}; + +#[allow(clippy::type_complexity)] +#[allow(async_fn_in_trait)] +pub trait InteractorHelpers { + fn interactor(&mut self) -> &mut Interactor; + fn state(&mut self) -> &mut State; + fn common_state(&mut self) -> &mut CommonState; + fn user_address(&self) -> &Address; + + fn prepare_transfer_data( + &self, + with_transfer_data: bool, + ) -> OptionalValue< + MultiValue3< + u64, + ManagedBuffer, + MultiValueEncoded>, + >, + > { + if with_transfer_data { + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::from(ManagedVec::>::from( + vec![ManagedBuffer::from("1")], + )); + OptionalValue::Some(MultiValue3::from((GAS_LIMIT, function, args))) + } else { + OptionalValue::None + } + } + + fn prepare_deposit_payments( + &mut self, + token: Option, + fee: Option>, + with_transfer_data: bool, + ) -> PaymentsVec { + let mut payment_vec = PaymentsVec::new(); + let fee_amount; + + // Add fee payment if present + if let Some(fee_struct) = fee { + fee_amount = self.calculate_fee_amount(fee_struct, with_transfer_data, token.clone()); + + if fee_amount > 0u64 { + let fee_payment = EgldOrEsdtTokenPayment::::new( + self.state().get_fee_token_identifier(), + 0, + fee_amount.clone(), + ); + payment_vec.push(fee_payment); + } + } + + // Add token payment if present + if let Some(token) = token { + let token_payment = EgldOrEsdtTokenPayment::::new( + token.token_id, + token.nonce, + token.amount.clone(), + ); + payment_vec.push(token_payment); + } + + payment_vec + } + + fn prepare_execute_payment( + &self, + token: Option, + ) -> ManagedVec> { + match token { + Some(token) => { + let token_data = EsdtTokenData { + amount: token.amount, + token_type: token.token_type, + ..Default::default() + }; + + let payment = OperationEsdtPayment::new(token.token_id, token.nonce, token_data); + + let mut payments = ManagedVec::new(); + payments.push(payment); + payments + } + _ => ManagedVec::new(), + } + } + + async fn prepare_operation( + &mut self, + shard: u32, + token: Option, + endpoint: Option<&str>, + ) -> Operation { + let user_address = self.user_address().clone(); + + let payment_vec = self.prepare_execute_payment(token); + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + + match endpoint { + Some(endpoint) => { + let gas_limit = 90_000_000u64; + let function = ManagedBuffer::::from(endpoint); + let args = ManagedVec::>::from(vec![ + ManagedBuffer::from("1"), + ]); + + let transfer_data = TransferData::new(gas_limit, function, args); + let operation_data = OperationData::new( + self.common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&user_address), + Some(transfer_data), + ); + + Operation::new( + ManagedAddress::from_address( + &self + .common_state() + .current_testing_sc_address() + .to_address(), + ), + payment_vec, + operation_data, + ) + } + None => { + let operation_data = OperationData::new( + self.common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&user_address), + None, + ); + + Operation::new( + ManagedAddress::from_address(self.user_address()), + payment_vec, + operation_data, + ) + } + } + } + + fn get_address_name(&mut self, address: &Bech32Address) -> &'static str { + let testing_addr = self.common_state().current_testing_sc_address(); + if address == testing_addr { + return TESTING_SC; + } + + let user_address = self.user_address(); + if address == user_address { + return USER_ADDRESS_STR; + } + + // Check shard-specific contract addresses + for shard_id in 0..3 { + let mvx_addr = self.common_state().get_mvx_esdt_safe_address(shard_id); + if address == mvx_addr { + return match shard_id { + 0 => MVX_ESDT_SAFE_SHARD_0, + 1 => MVX_ESDT_SAFE_SHARD_1, + 2 => MVX_ESDT_SAFE_SHARD_2, + _ => UNKNOWN_MVX_ESDT_SAFE, + }; + } + + let fee_addr = self.common_state().get_fee_market_address(shard_id); + if address == fee_addr { + return match shard_id { + 0 => FEE_MARKET_SHARD_0, + 1 => FEE_MARKET_SHARD_1, + 2 => FEE_MARKET_SHARD_2, + _ => UNKNOWN_FEE_MARKET, + }; + } + } + + "Unknown Address" + } + + fn calculate_fee_amount( + &self, + fee_struct: FeeStruct, + with_transfer_data: bool, + token: Option, + ) -> BigUint { + match &fee_struct.fee_type { + FeeType::Fixed { + per_transfer, + per_gas, + .. + } => { + match (with_transfer_data, token.is_some()) { + (true, true) => per_transfer.clone() + per_gas.clone() * GAS_LIMIT, // Transfer + SC call + (true, false) => per_gas.clone() * GAS_LIMIT, // SC call only + (false, _) => per_transfer.clone(), // Transfer only + } + } + FeeType::None => BigUint::zero(), + } + } + + fn generate_nonce_and_decimals(&mut self, token_type: EsdtTokenType) -> (u64, usize) { + match token_type { + EsdtTokenType::Fungible => (0, 18), + EsdtTokenType::MetaFungible | EsdtTokenType::DynamicMeta => (10, 18), + EsdtTokenType::NonFungible + | EsdtTokenType::NonFungibleV2 + | EsdtTokenType::DynamicNFT + | EsdtTokenType::SemiFungible + | EsdtTokenType::DynamicSFT => (10, 0), + _ => panic!("Unsupported token type for getting decimals and nonce"), + } + } + + fn create_standard_fee(&mut self) -> FeeStruct { + let per_transfer = BigUint::from(PER_TRANSFER); + let per_gas = BigUint::from(PER_GAS); + FeeStruct { + base_token: self.state().get_fee_token_identifier(), + fee_type: FeeType::Fixed { + token: self.state().get_fee_token_identifier(), + per_transfer, + per_gas, + }, + } + } + + fn get_bridge_service_for_shard(&mut self, shard_id: u32) -> Address { + match shard_id { + 0 => test_wallets::bob().to_address(), + 1 => test_wallets::dan().to_address(), + 2 => test_wallets::heidi().to_address(), + _ => panic!("Invalid shard ID: {shard_id}"), + } + } + fn get_bridge_owner_for_shard(&mut self, shard_id: u32) -> Address { + match shard_id { + 0 => test_wallets::mike().to_address(), + 1 => test_wallets::eve().to_address(), + 2 => test_wallets::judy().to_address(), + _ => panic!("Invalid shard ID: {shard_id}"), + } + } + + fn get_sovereign_owner_for_shard(&mut self, shard_id: u32) -> Address { + match shard_id { + 0 => { + let wallet_path = "wallets/wallet_shard_0.pem".to_string(); + let wallet = Wallet::from_pem_file(&wallet_path) + .unwrap_or_else(|_| panic!("Failed to load wallet for shard {}", shard_id)); + wallet.to_address() + } + 1 => test_wallets::frank().to_address(), + 2 => test_wallets::carol().to_address(), + _ => panic!("Invalid shard ID: {shard_id}"), + } + } + + fn decode_from_hex(&mut self, hex_string: &str) -> String { + let bytes = + hex::decode(hex_string).expect("Failed to decode hex string: invalid hex format"); + String::from_utf8(bytes).expect("Failed to decode UTF-8 string: invalid UTF-8 bytes") + } + + fn get_operation_hash(&mut self, operation: &Operation) -> ManagedBuffer { + let mut serialized_operation: ManagedBuffer = ManagedBuffer::new(); + let _ = operation.top_encode(&mut serialized_operation); + let sha256 = sha256(&serialized_operation.to_vec()); + + ManagedBuffer::new_from_bytes(&sha256) + } + + fn clone_token_with_amount( + &mut self, + token: EsdtTokenInfo, + new_amount: BigUint, + ) -> EsdtTokenInfo { + EsdtTokenInfo { + token_id: token.token_id, + amount: new_amount, + nonce: token.nonce, + decimals: token.decimals, + token_type: token.token_type, + } + } + + fn is_sovereign_token(&self, token: &EsdtTokenInfo) -> bool { + token + .token_id + .clone() + .into_managed_buffer() + .to_string() + .matches('-') + .count() + == 2 + } + + fn assert_expected_error_message( + &mut self, + response: Result<(), TxResponseStatus>, + expected_error_message: Option<&str>, + ) { + match response { + Ok(_) => { + assert!( + expected_error_message.is_none(), + "Expected error message: {:?}, but transaction was successful", + expected_error_message + ); + } + Err(error) => { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + } + + fn get_token_decimals(&self, token_type: EsdtTokenType) -> usize { + match token_type { + EsdtTokenType::NonFungibleV2 + | EsdtTokenType::DynamicNFT + | EsdtTokenType::SemiFungible + | EsdtTokenType::NonFungible + | EsdtTokenType::DynamicSFT => 0, + EsdtTokenType::Fungible | EsdtTokenType::MetaFungible | EsdtTokenType::DynamicMeta => { + 18 + } + _ => panic!("Unsupported token type for getting decimals"), + } + } + + fn get_token_by_type(&mut self, token_type: EsdtTokenType, index: usize) -> EsdtTokenInfo { + match token_type { + EsdtTokenType::NonFungibleV2 => self.state().get_nft_token_by_index(index), + EsdtTokenType::Fungible => self.state().get_fungible_token_by_index(index), + EsdtTokenType::SemiFungible => self.state().get_sft_token_by_index(index), + EsdtTokenType::MetaFungible => self.state().get_meta_esdt_token_by_index(index), + EsdtTokenType::DynamicNFT => self.state().get_dynamic_nft_token_by_index(index), + EsdtTokenType::DynamicSFT => self.state().get_dynamic_sft_token_by_index(index), + EsdtTokenType::DynamicMeta => self.state().get_dynamic_meta_esdt_token_by_index(index), + _ => panic!("Unsupported token type for test"), + } + } + + fn build_expected_deposit_log( + &mut self, + mut config: ActionConfig, + token: Option, + ) -> Vec> { + if config.shard != SHARD_1 { + return vec![]; + } + + let mut logs = Vec::new(); + let deposit_token = config.expected_deposit_token_log.clone().or(token); + + match deposit_token { + Some(t) => { + let topic_value = t.token_id.into_managed_buffer().to_string(); + logs.push(log!(DEPOSIT_EVENT, topics: [DEPOSIT_EVENT, topic_value])); + } + None => { + logs.push(log!(DEPOSIT_EVENT, topics: [SC_CALL_EVENT])); + } + } + + if let Some(additional_logs) = config.additional_logs.take() { + logs.extend(additional_logs); + } + + logs + } + + fn build_sovereign_deposit_logs( + &mut self, + main_token: &EsdtTokenInfo, + ) -> Vec> { + let main_token_id_topic = main_token + .token_id + .clone() + .into_managed_buffer() + .to_string(); + + let burn_log = match main_token.token_type { + EsdtTokenType::Fungible => { + log!(ESDT_LOCAL_BURN_FUNC_NAME, topics: [main_token_id_topic]) + } + _ => log!(ESDT_NFT_BURN_FUNC_NAME, topics: [main_token_id_topic]), + }; + + vec![burn_log] + } + + // CHECK BALANCE OPERATIONS + + async fn check_address_balance( + &mut self, + address: &Bech32Address, + expected_token_balance: Vec, + ) { + let address_name = self.get_address_name(address); + + let balances = self + .interactor() + .get_account_esdt(&address.to_address()) + .await; + + if expected_token_balance.is_empty() { + assert!( + balances.is_empty(), + "Expected no tokens for {} ({}), but found: {:?}", + address_name, + address, + balances + ); + return; + } + + for token_balance in expected_token_balance { + let token_id = &token_balance.token_id.into_managed_buffer().to_string(); + let expected_amount = &token_balance.amount; + + if *expected_amount == 0u64 { + match balances.get(token_id) { + None => {} + Some(esdt_balance) => { + panic!("For {} ({}) -> Expected token '{}' to be absent (balance 0), but found it with balance: {}", + address_name, address, token_id, esdt_balance.balance); + } + } + continue; + } + + let complete_tokens = balances.iter().find(|(key, _)| key.starts_with(token_id)); + + match complete_tokens { + Some((found_token_id, esdt_balance)) => { + let actual_amount = BigUint::from( + num_bigint::BigUint::parse_bytes(esdt_balance.balance.as_bytes(), 10) + .expect(FAILED_TO_PARSE_AS_NUMBER), + ); + assert_eq!( + actual_amount, + *expected_amount, + "\nFor {} ({}) -> Balance mismatch for token {}:\nexpected: {}\nfound: {}", + address_name, + address, + found_token_id, + expected_amount.to_display(), + esdt_balance.balance + ); + } + None => { + panic!( + "For {} ({}) -> Expected token starting with '{}' with balance {}, but none was found", + address_name, + address, + token_id, + expected_amount.to_display() + ); + } + } + } + } + + async fn check_user_balance(&mut self, expected_tokens: Vec) { + let user_address = Bech32Address::from(self.user_address().clone()); + self.check_address_balance(&user_address, expected_tokens) + .await; + } + + // NOTE: This is a temporary workaround, the whole balance check method needs refactoring + async fn check_mvx_esdt_balance(&mut self, shard: u32, expected_tokens: Vec) { + let mvx_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let tokens = if expected_tokens.is_empty() { + let mut tokens = self.create_empty_balance_state().await; + tokens.retain(|token| { + !token + .token_id + .clone() + .into_managed_buffer() + .to_string() + .contains(TRUSTED_TOKEN_NAME) + }); + tokens + } else { + expected_tokens + }; + self.check_address_balance(&mvx_address, tokens).await; + } + + async fn check_address_egld_balance(&mut self, address: &Bech32Address, expected_amount: u64) { + let balance = self + .interactor() + .get_account(&address.clone().into_address()) + .await + .balance; + assert_eq!( + balance, + expected_amount.to_string(), + "EGLD balance mismatch for {:?} :\n", + address, + ); + } + + async fn check_fee_market_balance(&mut self, shard: u32, expected_tokens: Vec) { + let fee_market_address = self.common_state().get_fee_market_address(shard).clone(); + let tokens = if expected_tokens.is_empty() { + vec![self + .common_state() + .get_fee_market_token_for_shard_converted(shard)] + } else { + expected_tokens + }; + self.check_address_balance(&fee_market_address, tokens) + .await; + } + + // NOTE: This is a temporary workaround, the whole balance check method needs refactoring + async fn check_testing_sc_balance(&mut self, expected_tokens: Vec) { + let testing_sc_address = self.common_state().current_testing_sc_address().clone(); + let tokens = if expected_tokens.is_empty() { + let mut tokens = self.create_empty_balance_state().await; + tokens.retain(|token| { + !token + .token_id + .clone() + .into_managed_buffer() + .to_string() + .contains(TRUSTED_TOKEN_NAME) + }); + tokens + } else { + expected_tokens + }; + self.check_address_balance(&testing_sc_address, tokens) + .await; + } + + async fn check_user_balance_unchanged(&mut self) { + let expected_balance = self.state().get_initial_wallet_tokens_state().clone(); + self.check_user_balance(expected_balance).await; + } + + async fn check_contracts_empty(&mut self, shard: u32) { + self.check_mvx_esdt_balance(shard, Vec::new()).await; + self.check_testing_sc_balance(Vec::new()).await; + } + + async fn create_empty_balance_state(&mut self) -> Vec { + let mut empty_balance_state = self.state().get_initial_wallet_tokens_state().clone(); + for token in empty_balance_state.iter_mut() { + token.amount = BigUint::from(0u64); + } + empty_balance_state + } + + fn create_serializable_token( + &mut self, + token: EsdtTokenInfo, + amount: u64, + ) -> SerializableToken { + SerializableToken { + token_id: token.token_id.into_managed_buffer().to_string(), + token_type: token.token_type as u8, + nonce: token.nonce, + decimals: token.decimals, + amount, + } + } + + fn get_expected_mvx_tokens( + &mut self, + token: &Option, + amount: &Option>, + is_sov_mapped_token: bool, + is_execute: bool, + is_burn_mechanism_set: bool, + ) -> Vec { + // Sovereign mapped tokens without burn mechanism: only keep 1 SFT/META token + if is_sov_mapped_token && !is_burn_mechanism_set { + if let Some(token) = token { + if matches!( + token.token_type, + EsdtTokenType::MetaFungible + | EsdtTokenType::DynamicMeta + | EsdtTokenType::DynamicSFT + | EsdtTokenType::SemiFungible + ) { + return vec![self.clone_token_with_amount(token.clone(), BigUint::from(1u64))]; + } + } + return vec![]; + } + + // Non-sovereign deposits: full amount goes to MVX safe + if !is_sov_mapped_token && !is_execute && !is_burn_mechanism_set { + if let (Some(token), Some(amount)) = (token, amount) { + return vec![self.clone_token_with_amount(token.clone(), amount.clone())]; + } + } + + // All other cases: no tokens remain in MVX safe + vec![] + } + + /// For user we have two cases: + /// 1. User should get tokens back after execute call (with_transfer_data = false) + /// 2. User should not get tokens back after execute call (with_transfer_data = true) + /// + /// For MVX we have two cases: + /// 1. Tokens are deposited to MVX ESDT Safe + /// 2. Tokens leave the contract after operations or are burned (special case for the 1 SFT/META sov token that stays in the sc) + async fn check_balances_after_action(&mut self, bcc: BalanceCheckConfig) { + let BalanceCheckConfig { + shard, + token, + amount, + fee, + with_transfer_data, + is_execute, + is_burn_mechanism_set, + expected_error, + } = bcc; + + let is_sov_mapped_token = token + .as_ref() + .map(|t| { + t.clone() + .token_id + .into_managed_buffer() + .to_string() + .split('-') + .next() + == Some("SOV") + }) + .unwrap_or(false); + + let is_egld = token.clone().map(|t| t.token_id.is_egld()).unwrap_or(false); + + let fee_amount = fee + .as_ref() + .map(|f| self.calculate_fee_amount(f.clone(), with_transfer_data, token.clone())) + .unwrap_or_else(BigUint::zero); + + let mut expected_user_tokens = Vec::new(); + + // USER tokens + if let (Some(token), Some(amount)) = (token.clone(), amount.clone()) { + let token_id = token.token_id.clone(); + let initial_user_balance = self + .state() + .get_initial_wallet_token_balance(token_id.clone()); + + let user_should_get_token_back = is_execute && !with_transfer_data; + + let remaining_amount = match (user_should_get_token_back, is_sov_mapped_token) { + (true, true) => amount, + (true, false) => initial_user_balance, + (false, _) => Self::safe_subtract(initial_user_balance, amount.clone()), + }; + + expected_user_tokens + .push(self.clone_token_with_amount(token.clone(), remaining_amount)); + } + + if fee.is_some() && fee_amount > 0u64 { + let fee_token = self.state().get_fee_token_id(); + let initial_fee_balance = fee_token.clone().amount; + let remaining_fee = Self::safe_subtract(initial_fee_balance, fee_amount.clone()); + expected_user_tokens.push(self.clone_token_with_amount(fee_token, remaining_fee)); + } + + if expected_user_tokens.is_empty() || expected_error.is_some() { + self.check_user_balance_unchanged().await; + } else { + self.check_user_balance(expected_user_tokens).await; + } + + //MVX Tokens + if is_egld { + let current_balance = self.common_state().get_mvx_egld_balance_for_shard(shard); + let amount_u64 = amount.clone().unwrap().to_u64().expect(AMOUNT_IS_TOO_LARGE); + let expected_amount = if is_execute { + current_balance - amount_u64 + } else { + current_balance + amount_u64 + }; + let address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + self.check_address_egld_balance(&address, expected_amount) + .await; + self.common_state() + .update_mvx_egld_balance_with_amount(shard, expected_amount); + } else { + // ESDT tokens + let mvx_tokens = self.get_expected_mvx_tokens( + &token, + &amount, + is_sov_mapped_token, + is_execute, + is_burn_mechanism_set, + ); + self.check_mvx_esdt_balance(shard, mvx_tokens).await; + } + + // FEE market + if fee_amount > 0u64 { + let fee_token = self.state().get_fee_token_id(); + let previous_fee_amount = BigUint::from( + self.common_state() + .get_fee_market_token_amount_for_shard(shard), + ); + let expected_fee_tokens = + vec![self.clone_token_with_amount(fee_token, previous_fee_amount + fee_amount)]; + self.check_fee_market_balance(shard, expected_fee_tokens) + .await; + } else { + self.check_fee_market_balance(shard, vec![]).await; + } + + // TESTING SC + if is_egld && is_execute && with_transfer_data && expected_error.is_none() { + let expected_amount = self.common_state().get_testing_egld_balance() + + amount.clone().unwrap().to_u64().unwrap(); + let testing_address = self.common_state().current_testing_sc_address().clone(); + self.check_address_egld_balance(&testing_address, expected_amount) + .await; + self.common_state() + .update_testing_egld_balance_with_amount(expected_amount); + } else { + let testing_sc_tokens = match (&token, &amount) { + (Some(token), Some(amount)) => { + if is_execute && with_transfer_data && expected_error.is_none() { + vec![self.clone_token_with_amount(token.clone(), amount.clone())] + } else { + vec![] + } + } + _ => vec![], + }; + + self.check_testing_sc_balance(testing_sc_tokens).await; + } + } + + /// Key and value should be in hex + async fn check_account_storage( + &mut self, + address: Address, + wanted_key: &str, + expected_value: Option<&str>, + ) { + let pairs = self.interactor().get_account_storage(&address).await; + + let found_entry = pairs.iter().find(|(key, _)| key.contains(wanted_key)); + + let decoded_key = self.decode_from_hex(wanted_key); + + match expected_value { + Some(expected) => { + assert!( + found_entry.is_some(), + "Expected key containing '{}' (decoded: '{}') was not found in account storage.", + wanted_key, + decoded_key + ); + + let (_, value) = found_entry.unwrap(); + + let decoded_expected = self.decode_from_hex(expected); + + let decoded_value = self.decode_from_hex(value); + + assert!( + value.contains(expected), + "Mismatch: expected '{}' (decoded: '{}') to be contained in '{}' (decoded: '{}')", + expected, + decoded_expected, + value, + decoded_value, + ); + } + None => { + assert!( + found_entry.is_none(), + "Did not expect to find key containing '{}' (decoded: '{}') in account storage.", + wanted_key, + decoded_key + ); + } + } + } + + fn safe_subtract(a: BigUint, b: BigUint) -> BigUint { + if a > b { + a - b + } else { + BigUint::zero() + } + } + + fn is_nft(&self, token: &EsdtTokenInfo) -> bool { + matches!( + token.token_type, + EsdtTokenType::NonFungibleV2 | EsdtTokenType::DynamicNFT | EsdtTokenType::NonFungible + ) + } + + fn generate_random_chain_id() -> String { + rand::rng() + .sample_iter(&Alphanumeric) + .filter(|c| c.is_ascii_alphabetic() && c.is_ascii_lowercase()) + .take(4) + .map(char::from) + .collect() + } + + fn load_wallet(wallet_path: &Path, test_id: u64) -> Wallet { + if wallet_path.exists() { + Wallet::from_pem_file(wallet_path.to_str().unwrap()).unwrap_or_else(|_| { + panic!( + "Failed to load {} for test {}", + wallet_path.display(), + test_id + ) + }) + } else { + panic!("{} not found for test {}", wallet_path.display(), test_id); + } + } + + fn create_random_sovereign_token_id(&mut self, shard: u32) -> String { + let current_chain_id = self.common_state().get_chain_id_for_shard(shard).clone(); + let rand_string: String = rand::rng() + .sample_iter(&Alphanumeric) + .filter(|c| c.is_ascii_alphanumeric() && c.is_ascii_lowercase()) + .take(6) + .map(char::from) + .collect(); + format!("{}-SOV-{}", current_chain_id, rand_string) + } + + async fn update_fee_market_balance_state( + &mut self, + fee: Option>, + payment_vec: PaymentsVec, + shard: u32, + ) { + if fee.is_none() || payment_vec.is_empty() { + return; + } + let mut fee_token_in_fee_market = self.common_state().get_fee_market_token_for_shard(shard); + + let payment = payment_vec.get(0); + if let Some(payment_amount) = payment.amount.to_u64() { + fee_token_in_fee_market.amount += payment_amount; + } + self.common_state() + .set_fee_market_token_for_shard(shard, fee_token_in_fee_market); + } +} diff --git a/common/common-interactor/src/interactor_state.rs b/common/common-interactor/src/interactor_state.rs new file mode 100644 index 000000000..3ffacc106 --- /dev/null +++ b/common/common-interactor/src/interactor_state.rs @@ -0,0 +1,209 @@ +#![allow(non_snake_case)] + +use error_messages::{ + NO_ADDRESSES_AVAILABLE, NO_KNOWN_DYNAMIC_META_ESDT_TOKEN_ID, NO_KNOWN_DYNAMIC_NFT_TOKEN_ID, + NO_KNOWN_DYNAMIC_SFT_TOKEN_ID, NO_KNOWN_FEE_TOKEN, NO_KNOWN_FIRST_TOKEN, + NO_KNOWN_FUNGIBLE_TOKEN, NO_KNOWN_META_ESDT_TOKEN, NO_KNOWN_NFT_TOKEN, NO_KNOWN_SFT_TOKEN, + NO_KNOWN_TRUSTED_TOKEN, +}; +use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone)] +pub struct EsdtTokenInfo { + pub token_id: EgldOrEsdtTokenIdentifier, + pub nonce: u64, + pub token_type: EsdtTokenType, + pub decimals: usize, + pub amount: BigUint, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AddressInfo { + pub address: Bech32Address, + pub chain_id: String, +} + +// NOTE: This struct holds deployed contract addresses. +// The index of each address corresponds to the shard number where the contract was deployed. +// For example, index 0 = shard 0, index 1 = shard 1, etc. +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct ShardAddresses { + pub addresses: Vec, +} + +impl ShardAddresses { + pub fn push(&mut self, address: AddressInfo) -> usize { + self.addresses.push(address); + self.addresses.len() - 1 + } + + pub fn first(&self) -> &Bech32Address { + &self + .addresses + .first() + .expect(NO_ADDRESSES_AVAILABLE) + .address + } +} + +#[derive(Debug, Default)] +pub struct State { + pub fungible_tokens: Vec, + pub fee_token: Option, + pub nft_tokens: Vec, + pub meta_esdt_tokens: Vec, + pub dynamic_nft_tokens: Vec, + pub dynamic_sft_tokens: Vec, + pub dynamic_meta_esdt_tokens: Vec, + pub sft_tokens: Vec, + pub initial_wallet_tokens_state: Vec, + pub trusted_token: Option, +} + +impl State { + pub fn add_fungible_token(&mut self, token: EsdtTokenInfo) { + self.fungible_tokens.push(token); + } + + pub fn set_fee_token(&mut self, token: EsdtTokenInfo) { + self.fee_token = Some(token); + } + + pub fn add_nft_token(&mut self, token: EsdtTokenInfo) { + self.nft_tokens.push(token); + } + + pub fn add_meta_esdt_token(&mut self, token: EsdtTokenInfo) { + self.meta_esdt_tokens.push(token); + } + + pub fn add_dynamic_nft_token(&mut self, token: EsdtTokenInfo) { + self.dynamic_nft_tokens.push(token); + } + + pub fn add_sft_token(&mut self, token: EsdtTokenInfo) { + self.sft_tokens.push(token); + } + + pub fn add_dynamic_sft_token(&mut self, token: EsdtTokenInfo) { + self.dynamic_sft_tokens.push(token); + } + + pub fn add_dynamic_meta_esdt_token(&mut self, token: EsdtTokenInfo) { + self.dynamic_meta_esdt_tokens.push(token); + } + + pub fn update_or_add_initial_wallet_token(&mut self, token: EsdtTokenInfo) { + if let Some(existing_token) = self + .initial_wallet_tokens_state + .iter_mut() + .find(|t| t.token_id == token.token_id && t.nonce == token.nonce) + { + existing_token.amount += token.amount; + } else { + self.initial_wallet_tokens_state.push(token); + } + } + + pub fn set_trusted_token(&mut self, token: EsdtTokenInfo) { + self.trusted_token = Some(token); + } + + pub fn get_trusted_token(&self) -> EsdtTokenInfo { + self.trusted_token + .as_ref() + .expect(NO_KNOWN_TRUSTED_TOKEN) + .clone() + } + + pub fn get_first_fungible_token_identifier(&self) -> EgldOrEsdtTokenIdentifier { + self.fungible_tokens + .first() + .expect(NO_KNOWN_FIRST_TOKEN) + .token_id + .clone() + } + + pub fn get_fee_token_identifier(&self) -> EgldOrEsdtTokenIdentifier { + self.fee_token + .as_ref() + .expect(NO_KNOWN_FEE_TOKEN) + .token_id + .clone() + } + + pub fn get_first_fungible_token_id(&self) -> EsdtTokenInfo { + self.fungible_tokens + .first() + .expect(NO_KNOWN_FIRST_TOKEN) + .clone() + } + + pub fn get_fee_token_id(&self) -> EsdtTokenInfo { + self.fee_token.as_ref().expect(NO_KNOWN_FEE_TOKEN).clone() + } + + pub fn get_fungible_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.fungible_tokens + .get(index) + .expect(NO_KNOWN_FUNGIBLE_TOKEN) + .clone() + } + + pub fn get_nft_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.nft_tokens + .get(index) + .expect(NO_KNOWN_NFT_TOKEN) + .clone() + } + + pub fn get_meta_esdt_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.meta_esdt_tokens + .get(index) + .expect(NO_KNOWN_META_ESDT_TOKEN) + .clone() + } + + pub fn get_dynamic_nft_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.dynamic_nft_tokens + .get(index) + .expect(NO_KNOWN_DYNAMIC_NFT_TOKEN_ID) + .clone() + } + + pub fn get_sft_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.sft_tokens + .get(index) + .expect(NO_KNOWN_SFT_TOKEN) + .clone() + } + + pub fn get_dynamic_sft_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.dynamic_sft_tokens + .get(index) + .expect(NO_KNOWN_DYNAMIC_SFT_TOKEN_ID) + .clone() + } + + pub fn get_dynamic_meta_esdt_token_by_index(&self, index: usize) -> EsdtTokenInfo { + self.dynamic_meta_esdt_tokens + .get(index) + .expect(NO_KNOWN_DYNAMIC_META_ESDT_TOKEN_ID) + .clone() + } + + pub fn get_initial_wallet_tokens_state(&self) -> &Vec { + &self.initial_wallet_tokens_state + } + + pub fn get_initial_wallet_token_balance( + &self, + token_id: EgldOrEsdtTokenIdentifier, + ) -> BigUint { + self.initial_wallet_tokens_state + .iter() + .find(|token| token.token_id == token_id) + .map_or_else(BigUint::zero, |token| token.amount.clone()) + } +} diff --git a/common/common-interactor/src/interactor_structs.rs b/common/common-interactor/src/interactor_structs.rs new file mode 100644 index 000000000..72957f208 --- /dev/null +++ b/common/common-interactor/src/interactor_structs.rs @@ -0,0 +1,145 @@ +use common_test_setup::base_setup::init::ExpectedLogs; +use multiversx_sc::{ + imports::Bech32Address, + types::{BigUint, EsdtTokenType}, +}; +use multiversx_sc_snippets::imports::StaticApi; +use serde::{Deserialize, Serialize}; +use structs::fee::FeeStruct; + +use crate::interactor_state::EsdtTokenInfo; + +pub struct IssueTokenStruct { + pub token_display_name: String, + pub token_ticker: String, + pub token_type: EsdtTokenType, + pub num_decimals: usize, +} +#[derive(Clone)] +pub struct MintTokenStruct { + pub name: Option, + pub amount: BigUint, + pub attributes: Option>, +} + +#[derive(Clone, Default)] +pub struct ActionConfig { + pub shard: u32, + pub expected_error: Option, + pub additional_logs: Option>>, + pub expected_deposit_token_log: Option, + pub expected_log_error: Option<&'static str>, + pub with_transfer_data: Option, + pub endpoint: Option, +} + +#[derive(Clone, Default, Serialize, Deserialize, Debug)] +pub struct SerializableToken { + pub token_id: String, + pub nonce: u64, + pub token_type: u8, + pub decimals: usize, + pub amount: u64, +} + +impl ActionConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn shard(mut self, shard: u32) -> Self { + self.shard = shard; + self + } + + pub fn additional_logs(mut self, logs: Vec>) -> Self { + if let Some(existing_logs) = &mut self.additional_logs { + existing_logs.extend(logs); + } else { + self.additional_logs = Some(logs); + } + self + } + + pub fn expected_deposit_token_log(mut self, token: EsdtTokenInfo) -> Self { + self.expected_deposit_token_log = Some(token); + self + } + + pub fn with_endpoint(mut self, endpoint: String) -> Self { + self.endpoint = Some(endpoint); + self.with_transfer_data = Some(true); + self + } + + pub fn expected_log_error(mut self, value: &'static str) -> Self { + self.expected_log_error = Some(value); + self + } +} + +#[derive(Clone, Default)] +pub struct BalanceCheckConfig { + pub shard: u32, + pub token: Option, + pub amount: Option>, + pub fee: Option>, + pub with_transfer_data: bool, + pub is_execute: bool, + pub expected_error: Option<&'static str>, + pub is_burn_mechanism_set: bool, +} + +impl BalanceCheckConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn shard(mut self, shard: u32) -> Self { + self.shard = shard; + self + } + + pub fn token(mut self, token: Option) -> Self { + self.token = token; + self + } + + pub fn amount(mut self, amount: BigUint) -> Self { + self.amount = Some(amount); + self + } + + pub fn fee(mut self, fee: Option>) -> Self { + self.fee = fee; + self + } + + pub fn with_transfer_data(mut self, value: bool) -> Self { + self.with_transfer_data = value; + self + } + + pub fn is_execute(mut self, value: bool) -> Self { + self.is_execute = value; + self + } + + pub fn expected_error(mut self, value: Option<&'static str>) -> Self { + self.expected_error = value; + self + } + + pub fn is_burn_mechanism_set(mut self, value: bool) -> Self { + self.is_burn_mechanism_set = value; + self + } +} + +#[derive(Clone)] +pub struct TemplateAddresses { + pub chain_config_address: Bech32Address, + pub header_verifier_address: Bech32Address, + pub esdt_safe_address: Bech32Address, + pub fee_market_address: Bech32Address, +} diff --git a/common/common-interactor/src/lib.rs b/common/common-interactor/src/lib.rs new file mode 100644 index 000000000..3b554e708 --- /dev/null +++ b/common/common-interactor/src/lib.rs @@ -0,0 +1,6 @@ +pub mod common_sovereign_interactor; +pub mod interactor_common_state; +pub mod interactor_config; +pub mod interactor_helpers; +pub mod interactor_state; +pub mod interactor_structs; diff --git a/common/common-test-setup/Cargo.toml b/common/common-test-setup/Cargo.toml index e7683007e..093529405 100644 --- a/common/common-test-setup/Cargo.toml +++ b/common/common-test-setup/Cargo.toml @@ -6,8 +6,18 @@ edition = "2021" [lib] path = "src/lib.rs" +[dependencies] +rand_core = "0.6.4" +rand = "0.8.5" +hex = "0.4.3" +base64 = "0.21" + +[dependencies.multiversx-sc] +version = "0.63.0" + [dependencies.multiversx-sc-scenario] -version = "0.57.1" +version = "0.63.0" +features = ["bls"] [dependencies.proxies] path = "../proxies" @@ -18,8 +28,8 @@ path = "../structs" [dependencies.cross-chain] path = "../cross-chain" -[dependencies.fee-market] -path = "../../fee-market" +[dependencies.mvx-fee-market] +path = "../../mvx-fee-market" [dependencies.header-verifier] path = "../../header-verifier" @@ -32,3 +42,15 @@ path = "../../testing-sc" [dependencies.mvx-esdt-safe] path = "../../mvx-esdt-safe" + +[dependencies.chain-factory] +path = "../../chain-factory" + +[dependencies.sovereign-forge] +path = "../../sovereign-forge" + +[dependencies.sov-fee-market] +path = "../../sov-fee-market" + +[dependencies.error-messages] +path = "../error-messages" diff --git a/common/common-test-setup/src/base_setup/checks.rs b/common/common-test-setup/src/base_setup/checks.rs new file mode 100644 index 000000000..ac49096c8 --- /dev/null +++ b/common/common-test-setup/src/base_setup/checks.rs @@ -0,0 +1,278 @@ +use cross_chain::storage::CrossChainStorage; +use error_messages::INCORRECT_DEPOSIT_AMOUNT; +use header_verifier::storage::HeaderVerifierStorageModule; +use multiversx_sc_scenario::imports::{EgldOrEsdtTokenIdentifier, ManagedVec}; +use multiversx_sc_scenario::DebugApi; +use multiversx_sc_scenario::{ + api::StaticApi, + imports::{ + Address, BigUint, ManagedAddress, ManagedBuffer, MultiValue3, ReturnsResultUnmanaged, + TestTokenIdentifier, + }, + multiversx_chain_vm::crypto_functions::sha256, + scenario_model::TxResponseStatus, + ScenarioTxRun, ScenarioTxWhitebox, +}; +use mvx_esdt_safe::bridging_mechanism::BridgingMechanism; +use proxies::mvx_fee_market_proxy::MvxFeeMarketProxy; +use structs::OperationHashStatus; + +use crate::base_setup::init::ErrorPayloadToString; +use crate::{ + base_setup::init::BaseSetup, + constants::{ + CHAIN_CONFIG_ADDRESS, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FIRST_TEST_TOKEN, + HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS, + }, +}; + +impl BaseSetup { + pub fn assert_optional_error_message( + &mut self, + response: Option, + expected_error_message: Option<&str>, + ) { + match response { + None => assert!( + expected_error_message.is_none(), + "Transaction was successful, but expected error" + ), + Some(payload) => { + let error_message_str = payload.to_error_string(); + assert_eq!( + Some(error_message_str.as_str()), + expected_error_message, + "Expected error message did not match" + ); + } + } + } + + pub fn check_account_multiple_esdts( + &mut self, + address: Address, + tokens: Vec>>, + ) { + for token in tokens { + let (token_id, nonce, amount) = token.into_tuple(); + self.world + .check_account(&address) + .esdt_nft_balance_and_attributes( + token_id, + nonce, + amount, + ManagedBuffer::::new(), + ); + } + } + + pub fn query_user_fee_whitelist( + &mut self, + users_to_query: Option<&[ManagedAddress]>, + ) { + let query = self + .world + .query() + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .users_whitelist() + .returns(ReturnsResultUnmanaged) + .run(); + + match users_to_query { + Some(expected_users) => { + assert!(query + .iter() + .all(|u| expected_users.contains(&ManagedAddress::from(u)))) + } + None => { + assert!(query.is_empty()) + } + } + } + + pub fn check_account_single_esdt( + &mut self, + address: Address, + token_id: TestTokenIdentifier, + nonce: u64, + expected_balance: BigUint, + ) { + self.world + .check_account(address) + .esdt_nft_balance_and_attributes( + token_id, + nonce, + expected_balance, + ManagedBuffer::::new(), + ); + } + + pub fn check_bls_key_for_epoch_in_header_verifier( + &mut self, + epoch: u64, + registered_bls_keys: &ManagedVec>, + ) { + // Convert ManagedVec<...> -> Vec (hex encoded) + let bls_keys_hex: Vec = registered_bls_keys + .iter() + .map(|buffer| { + let bytes = buffer.to_boxed_bytes(); + hex::encode(bytes) // encode each buffer as a hex string + }) + .collect(); + + // Borrow as &str for iteration + let bls_keys: Vec<&str> = bls_keys_hex.iter().map(|s| s.as_str()).collect(); + + // Query contract and assert + self.world.query().to(HEADER_VERIFIER_ADDRESS).whitebox( + header_verifier::contract_obj, + |sc| { + for bls_key_hex in bls_keys { + // Convert hex string back to bytes and build ManagedBuffer + let key_bytes = hex::decode(bls_key_hex).unwrap(); + let buffer = ManagedBuffer::new_from_bytes(&key_bytes); + + assert!( + sc.bls_pub_keys(epoch).contains(&buffer), + "BLS key not found in header verifier: {}", + bls_key_hex + ); + } + }, + ); + } + + pub fn check_deposited_tokens_amount( + &mut self, + tokens: Vec<(EgldOrEsdtTokenIdentifier, u64)>, + ) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + let tokens: Vec<(EgldOrEsdtTokenIdentifier, u64)> = tokens + .into_iter() + .map(|(token_id, amount)| { + let token_id_bytes = token_id.to_boxed_bytes(); + ( + EgldOrEsdtTokenIdentifier::::from(token_id_bytes.as_slice()), + amount, + ) + }) + .collect(); + for token in tokens { + let (token_id, amount) = token; + assert!( + sc.deposited_tokens_amount(&token_id).get() == amount, + "{}", + INCORRECT_DEPOSIT_AMOUNT + ); + } + }); + } + + pub fn check_multiversx_to_sovereign_token_id_mapper_is_empty(&mut self, token_name: &str) { + self.world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc + .multiversx_to_sovereign_token_id_mapper(&EgldOrEsdtTokenIdentifier::from( + token_name + )) + .is_empty()); + }); + } + + pub fn check_operation_hash_status_is_empty( + &mut self, + operation_hash: &ManagedBuffer, + ) { + self.world.query().to(HEADER_VERIFIER_ADDRESS).whitebox( + header_verifier::contract_obj, + |sc| { + let operation_hash_whitebox = + ManagedBuffer::new_from_bytes(&operation_hash.to_vec()); + let hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&operation_hash_whitebox.to_vec())); + + assert!(sc + .operation_hash_status(&hash_of_hashes, &operation_hash_whitebox) + .is_empty()); + }, + ) + } + + pub fn check_operation_hash_status( + &mut self, + operation_hash: &ManagedBuffer, + status: OperationHashStatus, + ) { + self.world.query().to(HEADER_VERIFIER_ADDRESS).whitebox( + header_verifier::contract_obj, + |sc| { + let operation_hash_whitebox = + ManagedBuffer::new_from_bytes(&operation_hash.to_vec()); + let hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&operation_hash_whitebox.to_vec())); + + assert!( + sc.operation_hash_status(&hash_of_hashes, &operation_hash_whitebox) + .get() + == status + ); + }, + ) + } + + pub fn assert_expected_error_message( + &mut self, + response: Result<(), TxResponseStatus>, + expected_error_message: Option<&str>, + ) { + match response { + Ok(_) => assert!( + expected_error_message.is_none(), + "Transaction was successful, but expected error" + ), + Err(error) => { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + } + + pub fn assert_contract_and_owner_balances( + &mut self, + contract_egld: &BigUint, + contract_token: &BigUint, + owner_egld: &BigUint, + owner_token: &BigUint, + ) { + self.world + .check_account(CHAIN_CONFIG_ADDRESS) + .balance(contract_egld); + self.world + .check_account(CHAIN_CONFIG_ADDRESS) + .esdt_balance(FIRST_TEST_TOKEN, contract_token); + self.world.check_account(OWNER_ADDRESS).balance(owner_egld); + self.world + .check_account(OWNER_ADDRESS) + .esdt_balance(FIRST_TEST_TOKEN, owner_token); + } + + pub fn assert_contract_and_owner_token_balances( + &mut self, + contract_token: &BigUint, + owner_token: &BigUint, + ) { + self.world + .check_account(CHAIN_CONFIG_ADDRESS) + .esdt_balance(FIRST_TEST_TOKEN, contract_token); + self.world + .check_account(OWNER_ADDRESS) + .esdt_balance(FIRST_TEST_TOKEN, owner_token); + } +} diff --git a/common/common-test-setup/src/base_setup/complete_setup_phase.rs b/common/common-test-setup/src/base_setup/complete_setup_phase.rs new file mode 100644 index 000000000..003d51534 --- /dev/null +++ b/common/common-test-setup/src/base_setup/complete_setup_phase.rs @@ -0,0 +1,88 @@ +use multiversx_sc_scenario::{ + imports::{ReturnsHandledOrError, ReturnsResultUnmanaged}, + ScenarioTxRun, +}; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, header_verifier_proxy::HeaderverifierProxy, + mvx_esdt_safe_proxy::MvxEsdtSafeProxy, mvx_fee_market_proxy::MvxFeeMarketProxy, + sovereign_forge_proxy::SovereignForgeProxy, +}; + +use crate::{ + base_setup::init::BaseSetup, + constants::{ + CHAIN_CONFIG_ADDRESS, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, HEADER_VERIFIER_ADDRESS, + OWNER_ADDRESS, SOVEREIGN_FORGE_SC_ADDRESS, + }, +}; + +impl BaseSetup { + pub fn complete_header_verifier_setup_phase(&mut self, expected_error_message: Option<&str>) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .typed(HeaderverifierProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } + + pub fn complete_fee_market_setup_phase(&mut self) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.change_ownership_to_header_verifier(FEE_MARKET_ADDRESS); + + self.assert_expected_error_message(response, None); + } + + pub fn complete_sovereign_forge_setup_phase(&mut self, expected_error_message: Option<&str>) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } + + pub fn complete_chain_config_setup_phase(&mut self) { + let transaction = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(transaction, None); + } + + pub fn complete_mvx_esdt_safe_setup_phase(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() + .returns(ReturnsResultUnmanaged) + .run(); + } +} diff --git a/common/common-test-setup/src/base_setup/contract_endpoints.rs b/common/common-test-setup/src/base_setup/contract_endpoints.rs new file mode 100644 index 000000000..6e3f6f3ca --- /dev/null +++ b/common/common-test-setup/src/base_setup/contract_endpoints.rs @@ -0,0 +1,319 @@ +use crate::base_setup::init::ExpectedLogs; +use crate::base_setup::log_validations::assert_expected_logs; +use crate::constants::{ + EXECUTED_BRIDGE_OP_EVENT, REGISTER_BLS_KEY_ENDPOINT, SOVEREIGN_FORGE_SC_ADDRESS, + UNREGISTER_BLS_KEY_ENDPOINT, +}; +use crate::log; +use crate::{ + base_setup::init::BaseSetup, + constants::{CHAIN_CONFIG_ADDRESS, FEE_MARKET_ADDRESS, HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS}, +}; +use header_verifier::storage::HeaderVerifierStorageModule; +use multiversx_sc::imports::OptionalValue; +use multiversx_sc_scenario::api::{DebugApiBackend, VMHooksApi}; +use multiversx_sc_scenario::imports::{BigUint, ManagedVec, ReturnsResult, StorageClearable}; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_scenario::ScenarioTxWhitebox; +use multiversx_sc_scenario::{ + api::StaticApi, + imports::{ + ManagedBuffer, MultiEgldOrEsdtPayment, MultiValueEncoded, ReturnsHandledOrError, + TestAddress, + }, + ReturnsLogs, ScenarioTxRun, +}; +use proxies::sovereign_forge_proxy::SovereignForgeProxy; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, header_verifier_proxy::HeaderverifierProxy, + mvx_fee_market_proxy::MvxFeeMarketProxy, +}; +use structs::aliases::TxNonce; +use structs::fee::FeeStruct; +use structs::generate_hash::GenerateHash; +use structs::{ValidatorData, ValidatorOperation}; + +impl BaseSetup { + pub fn register_operation( + &mut self, + caller: TestAddress, + signature: ManagedBuffer, + hash_of_hashes: &ManagedBuffer, + bitmap: ManagedBuffer, + epoch: u64, + operations_hashes: MultiValueEncoded>, + ) { + self.world + .tx() + .from(caller) + .to(HEADER_VERIFIER_ADDRESS) + .typed(HeaderverifierProxy) + .register_bridge_operations(signature, hash_of_hashes, bitmap, epoch, operations_hashes) + .run(); + } + + pub fn set_fee_during_setup_phase( + &mut self, + fee_struct: FeeStruct, + error_message: Option<&str>, + ) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .set_fee_during_setup_phase(fee_struct) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, error_message); + } + + pub fn register( + &mut self, + bls_key: &ManagedBuffer, + payment: &MultiEgldOrEsdtPayment, + expected_error_message: Option<&str>, + ) { + let (response, _) = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .register(bls_key) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } + + pub fn unregister(&mut self, bls_key: &ManagedBuffer, expect_error: Option<&str>) { + let (response, _) = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .unregister(bls_key) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.assert_expected_error_message(response, expect_error); + } + + pub fn register_trusted_token(&mut self, trusted_token: &str) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .register_trusted_token(ManagedBuffer::from(trusted_token)) + .run(); + } + + pub fn register_validator( + &mut self, + hash_of_hashes: &ManagedBuffer, + validator_data: ValidatorData, + operation_nonce: TxNonce, + ) { + let (response, logs) = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .register_bls_key( + hash_of_hashes, + ValidatorOperation { + validator_data, + nonce: operation_nonce, + }, + ) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.assert_expected_error_message(response, None); + + let expected_logs = + vec![log!(REGISTER_BLS_KEY_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + assert_expected_logs(logs, expected_logs); + } + + pub fn register_validator_operation( + &mut self, + validator_data: ValidatorData, + _signature: ManagedBuffer, + bitmap: ManagedBuffer, + epoch: u64, + ) { + let validator_data_hash = validator_data.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&validator_data_hash.to_vec())); + + let (new_signature, pub_keys) = self.get_sig_and_pub_keys(1, &hash_of_hashes); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let pub_key = ManagedBuffer::new_from_bytes(&pub_keys[0].to_vec()); + sc.bls_pub_keys(0).clear(); + sc.bls_pub_keys(0).insert(pub_key); + }); + + self.register_operation( + OWNER_ADDRESS, + new_signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![validator_data_hash]), + ); + + let operation_nonce = self.next_operation_nonce(); + + self.register_validator(&hash_of_hashes, validator_data.clone(), operation_nonce); + + assert_eq!( + self.get_bls_key_id(&validator_data.bls_key), + validator_data.id + ); + } + + pub fn unregister_validator( + &mut self, + hash_of_hashes: &ManagedBuffer, + validator_operation: ValidatorOperation, + expected_error_message: Option<&str>, + ) { + let (response, logs) = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .unregister_bls_key(hash_of_hashes, validator_operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.assert_expected_error_message(response, None); + + let expected_logs = vec![log!( + UNREGISTER_BLS_KEY_ENDPOINT, + topics: [EXECUTED_BRIDGE_OP_EVENT], + data: expected_error_message + )]; + assert_expected_logs(logs, expected_logs); + } + + pub fn set_bls_keys_in_header_storage(&mut self, pub_keys: Vec>) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let mut new_pub_keys: ManagedVec< + VMHooksApi, + ManagedBuffer>, + > = ManagedVec::new(); + for pub_key in pub_keys.clone() { + let pub_key = ManagedBuffer::new_from_bytes(&pub_key.to_vec()); + new_pub_keys.push(pub_key); + } + sc.bls_pub_keys(0).clear(); + sc.bls_pub_keys(0).extend(new_pub_keys); + }); + } + + pub fn unregister_validator_via_bridge_operation( + &mut self, + validator_id: u32, + validator_bls_key: &ManagedBuffer, + num_of_validators: u64, + bitmap: &ManagedBuffer, + epoch: u64, + ) { + let validator_data = ValidatorData { + id: BigUint::from(validator_id), + address: OWNER_ADDRESS.to_managed_address(), + bls_key: validator_bls_key.clone(), + }; + + let validator_operation = ValidatorOperation { + validator_data, + nonce: self.next_operation_nonce(), + }; + + let validator_data_hash = validator_operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&validator_data_hash.to_vec())); + let (signature, pub_keys) = + self.get_sig_and_pub_keys(num_of_validators as usize, &hash_of_hashes); + + self.set_bls_keys_in_header_storage(pub_keys); + self.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap.clone(), + epoch, + MultiValueEncoded::from_iter(vec![validator_data_hash]), + ); + + self.unregister_validator(&hash_of_hashes, validator_operation, None); + } + + pub fn unregister_validator_operation( + &mut self, + validator_data: ValidatorData, + _signature: ManagedBuffer, + bitmap: ManagedBuffer, + epoch: u64, + expected_error_message: Option<&str>, + ) { + let validator_data_hash = validator_data.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&validator_data_hash.to_vec())); + + let signer_count = self.calculate_signer_count(&bitmap); + let (new_signature, pub_keys) = self.get_sig_and_pub_keys(signer_count, &hash_of_hashes); + + self.update_validator_key_in_chain_config(&validator_data, &pub_keys); + + self.set_bls_keys_in_header_storage(pub_keys); + + self.register_operation( + OWNER_ADDRESS, + new_signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![validator_data_hash]), + ); + + let validator_operation = ValidatorOperation { + validator_data: validator_data.clone(), + nonce: self.next_operation_nonce(), + }; + + self.unregister_validator(&hash_of_hashes, validator_operation, expected_error_message); + + assert_eq!(self.get_bls_key_id(&validator_data.bls_key), 0); + } + + pub fn get_bls_key_id(&mut self, bls_key: &ManagedBuffer) -> BigUint { + self.world + .query() + .to(CHAIN_CONFIG_ADDRESS) + .typed(ChainConfigContractProxy) + .bls_key_to_id_mapper(bls_key) + .returns(ReturnsResult) + .run() + } +} diff --git a/common/common-test-setup/src/base_setup/deploy.rs b/common/common-test-setup/src/base_setup/deploy.rs new file mode 100644 index 000000000..203da150f --- /dev/null +++ b/common/common-test-setup/src/base_setup/deploy.rs @@ -0,0 +1,290 @@ +use multiversx_sc_scenario::{ + api::StaticApi, + imports::{ + BigUint, ManagedBuffer, MultiValueEncoded, OptionalValue, ReturnsHandledOrError, + TestSCAddress, + }, + ScenarioTxRun, +}; +use proxies::{ + chain_config_proxy::ChainConfigContractProxy, chain_factory_proxy::ChainFactoryContractProxy, + header_verifier_proxy::HeaderverifierProxy, mvx_esdt_safe_proxy::MvxEsdtSafeProxy, + mvx_fee_market_proxy::MvxFeeMarketProxy, sov_esdt_safe_proxy::SovEsdtSafeProxy, + sov_fee_market_proxy::SovFeeMarketProxy, sovereign_forge_proxy::SovereignForgeProxy, + testing_sc_proxy::TestingScProxy, +}; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, + forge::ScArray, +}; + +use crate::{ + base_setup::init::BaseSetup, + constants::{ + CHAIN_CONFIG_ADDRESS, CHAIN_CONFIG_CODE_PATH, CHAIN_FACTORY_CODE_PATH, + CHAIN_FACTORY_SC_ADDRESS, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_MARKET_CODE_PATH, + HEADER_VERIFIER_ADDRESS, HEADER_VERIFIER_CODE_PATH, MVX_ESDT_SAFE_CODE_PATH, OWNER_ADDRESS, + SOVEREIGN_FORGE_CODE_PATH, SOVEREIGN_FORGE_SC_ADDRESS, SOVEREIGN_TOKEN_PREFIX, + SOV_ESDT_SAFE_CODE_PATH, SOV_FEE_MARKET_ADDRESS, SOV_FEE_MARKET_CODE_PATH, + TESTING_SC_ADDRESS, TESTING_SC_CODE_PATH, + }, +}; + +impl BaseSetup { + pub fn deploy_mvx_esdt_safe( + &mut self, + opt_config: OptionalValue>, + ) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(MvxEsdtSafeProxy) + .init( + OWNER_ADDRESS.to_managed_address(), + SOVEREIGN_FORGE_SC_ADDRESS, + SOVEREIGN_TOKEN_PREFIX, + opt_config, + ) + .code(MVX_ESDT_SAFE_CODE_PATH) + .new_address(ESDT_SAFE_ADDRESS) + .run(); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .unpause_endpoint() + .run(); + + self + } + + pub fn deploy_fee_market( + &mut self, + fee: Option>, + esdt_safe_address: TestSCAddress, + ) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(MvxFeeMarketProxy) + .init(esdt_safe_address, fee) + .code(FEE_MARKET_CODE_PATH) + .new_address(FEE_MARKET_ADDRESS) + .run(); + + self + } + + pub fn deploy_sov_fee_market( + &mut self, + fee: Option>, + esdt_safe_address: TestSCAddress, + ) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(SovFeeMarketProxy) + .init(esdt_safe_address, fee) + .code(SOV_FEE_MARKET_CODE_PATH) + .new_address(SOV_FEE_MARKET_ADDRESS) + .run(); + + self + } + + pub fn deploy_testing_sc(&mut self) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(TestingScProxy) + .init() + .code(TESTING_SC_CODE_PATH) + .new_address(TESTING_SC_ADDRESS) + .run(); + + self + } + + pub fn deploy_header_verifier(&mut self, sovereign_contracts: Vec) -> &mut Self { + let contracts_array = self.get_contract_info_struct_for_sc_type(sovereign_contracts); + + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(HeaderverifierProxy) + .init(MultiValueEncoded::from_iter(contracts_array)) + .code(HEADER_VERIFIER_CODE_PATH) + .new_address(HEADER_VERIFIER_ADDRESS) + .run(); + + self + } + + pub fn deploy_chain_config( + &mut self, + opt_config: OptionalValue>, + expected_error_message: Option<&str>, + ) -> &mut Self { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .typed(ChainConfigContractProxy) + .init(opt_config) + .code(CHAIN_CONFIG_CODE_PATH) + .new_address(CHAIN_CONFIG_ADDRESS) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + + self + } + + pub fn deploy_chain_factory(&mut self) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(ChainFactoryContractProxy) + .init( + SOVEREIGN_FORGE_SC_ADDRESS, + CHAIN_CONFIG_ADDRESS, + HEADER_VERIFIER_ADDRESS, + ESDT_SAFE_ADDRESS, + FEE_MARKET_ADDRESS, + ) + .code(CHAIN_FACTORY_CODE_PATH) + .new_address(CHAIN_FACTORY_SC_ADDRESS) + .run(); + + self + } + + pub fn deploy_sovereign_forge( + &mut self, + deploy_cost: OptionalValue>, + ) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(SovereignForgeProxy) + .init(deploy_cost) + .code(SOVEREIGN_FORGE_CODE_PATH) + .new_address(SOVEREIGN_FORGE_SC_ADDRESS) + .run(); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .unpause_endpoint() + .run(); + + self + } + + pub fn deploy_sov_esdt_safe( + &mut self, + fee_market_address: TestSCAddress, + opt_config: OptionalValue>, + ) -> &mut Self { + self.world + .tx() + .from(OWNER_ADDRESS) + .typed(SovEsdtSafeProxy) + .init(fee_market_address, opt_config) + .code(SOV_ESDT_SAFE_CODE_PATH) + .new_address(ESDT_SAFE_ADDRESS) + .run(); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(SovEsdtSafeProxy) + .unpause_endpoint() + .run(); + + self + } + + pub fn deploy_phase_one( + &mut self, + payment: &BigUint, + opt_preferred_chain: Option>, + opt_config: OptionalValue>, + expected_error_message: Option<&str>, + ) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .deploy_phase_one(opt_preferred_chain, opt_config) + .egld(payment) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } + + pub fn deploy_phase_two( + &mut self, + error_message: Option<&str>, + opt_config: OptionalValue>, + ) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .deploy_phase_two(opt_config) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, error_message); + } + + pub fn deploy_phase_three( + &mut self, + fee: Option>, + expected_error_message: Option<&str>, + ) { + let fee = match fee { + Some(fee) => OptionalValue::Some(fee), + None => OptionalValue::None, + }; + + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .deploy_phase_three(fee) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } + + pub fn deploy_phase_four(&mut self, expected_error_message: Option<&str>) { + let response = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .deploy_phase_four() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.assert_expected_error_message(response, expected_error_message); + } +} diff --git a/common/common-test-setup/src/base_setup/helpers.rs b/common/common-test-setup/src/base_setup/helpers.rs new file mode 100644 index 000000000..d539b52a7 --- /dev/null +++ b/common/common-test-setup/src/base_setup/helpers.rs @@ -0,0 +1,240 @@ +use chain_config::storage::ChainConfigStorageModule; +use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; +use multiversx_sc::types::{ + BigUint, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, ManagedVec, MultiEgldOrEsdtPayment, +}; +use multiversx_sc_scenario::api::{DebugApiBackend, VMHooksApi}; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions_bls::create_aggregated_signature; +use multiversx_sc_scenario::ScenarioTxWhitebox; +use rand::RngCore; +use rand_core::OsRng; + +use multiversx_sc_scenario::imports::ManagedTypeApi; +use multiversx_sc_scenario::{ + api::StaticApi, + imports::{ManagedBuffer, ReturnsResultUnmanaged, TestSCAddress, TopEncode, UserBuiltinProxy}, + multiversx_chain_vm::crypto_functions::sha256, + ScenarioTxRun, +}; +use structs::ValidatorData; +use structs::{ + forge::{ContractInfo, ScArray}, + operation::Operation, + BLS_KEY_BYTE_LENGTH, +}; + +use crate::constants::{FIRST_TEST_TOKEN, NATIVE_TEST_TOKEN}; +use crate::{ + base_setup::init::BaseSetup, + constants::{ + CHAIN_CONFIG_ADDRESS, CHAIN_FACTORY_SC_ADDRESS, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, + HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS, + }, +}; + +impl BaseSetup { + pub fn get_native_token(&mut self) -> (ManagedBuffer, ManagedBuffer) { + (NATIVE_TEST_TOKEN.as_str().into(), "Native".into()) + } + pub fn register_multiple_validators(&mut self, new_validators: Vec>) { + for new_validator in new_validators { + self.register(&new_validator, &MultiEgldOrEsdtPayment::new(), None); + } + } + + pub fn combined_stake_payments( + &self, + amount: &BigUint, + ) -> MultiEgldOrEsdtPayment { + let mut payments = MultiEgldOrEsdtPayment::new(); + payments.push(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(EGLD_000000_TOKEN_IDENTIFIER.as_bytes()), + 0, + amount.clone(), + )); + payments.push(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_bytes()), + 0, + amount.clone(), + )); + + payments + } + + pub fn single_token_payment( + &self, + amount: &BigUint, + ) -> MultiEgldOrEsdtPayment { + let mut payments = MultiEgldOrEsdtPayment::new(); + payments.push(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_bytes()), + 0, + amount.clone(), + )); + + payments + } + + pub fn change_ownership_to_header_verifier(&mut self, sc_address: TestSCAddress) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(sc_address) + .typed(UserBuiltinProxy) + .change_owner_address(&HEADER_VERIFIER_ADDRESS.to_managed_address()) + .returns(ReturnsResultUnmanaged) + .run(); + } + + pub fn get_operation_hash( + &mut self, + operation: &Operation, + ) -> ManagedBuffer { + let mut serialized_operation: ManagedBuffer = ManagedBuffer::new(); + let _ = operation.top_encode(&mut serialized_operation); + let sha256 = sha256(&serialized_operation.to_vec()); + + ManagedBuffer::new_from_bytes(&sha256) + } + + pub fn get_contract_info_struct_for_sc_type( + &mut self, + sc_array: Vec, + ) -> Vec> { + sc_array + .iter() + .map(|sc| { + ContractInfo::new( + sc.clone(), + self.get_sc_address(sc.clone()).to_managed_address(), + ) + }) + .collect() + } + + pub fn get_sc_address(&'_ mut self, sc_type: ScArray) -> TestSCAddress<'_> { + match sc_type { + ScArray::ChainConfig => CHAIN_CONFIG_ADDRESS, + ScArray::ChainFactory => CHAIN_FACTORY_SC_ADDRESS, + ScArray::ESDTSafe => ESDT_SAFE_ADDRESS, + ScArray::HeaderVerifier => HEADER_VERIFIER_ADDRESS, + ScArray::FeeMarket => FEE_MARKET_ADDRESS, + } + } + + pub fn register_validators( + &mut self, + count: u64, + payments: &MultiEgldOrEsdtPayment, + ) -> Vec> { + let mut bls_keys = Vec::new(); + + for expected_id in 1..=count { + let bls_key = BLSKey::random(); + self.register(&bls_key, payments, None); + assert_eq!(self.get_bls_key_id(&bls_key), BigUint::from(expected_id),); + bls_keys.push(bls_key); + } + + bls_keys + } + + pub fn full_bitmap(&self, num_of_validators: u64) -> ManagedBuffer { + let mut bitmap_bytes = vec![0u8; num_of_validators.div_ceil(8) as usize]; + for index in 0..num_of_validators { + let byte_index = (index / 8) as usize; + let bit_index = (index % 8) as u8; + bitmap_bytes[byte_index] |= 1u8 << bit_index; + } + + ManagedBuffer::new_from_bytes(&bitmap_bytes) + } + + /// Creates a bitmap for the given validator indices + pub fn bitmap_for_signers(&self, validator_indices: &[u64]) -> ManagedBuffer { + if validator_indices.is_empty() { + return ManagedBuffer::new_from_bytes(&[]); + } + + let max_index = *validator_indices.iter().max().unwrap(); + let byte_len = (max_index / 8 + 1) as usize; + let mut bitmap_bytes = vec![0u8; byte_len]; + + for &validator_index in validator_indices { + let byte_index = (validator_index / 8) as usize; + let bit_index = (validator_index % 8) as u8; + bitmap_bytes[byte_index] |= 1u8 << bit_index; + } + + ManagedBuffer::new_from_bytes(&bitmap_bytes) + } + + pub fn get_sig_and_pub_keys( + &mut self, + pk_size: usize, + message: &ManagedBuffer, + ) -> (ManagedBuffer, Vec>) { + let (signature, pub_keys) = + create_aggregated_signature(pk_size, &message.to_vec()).unwrap(); + let pk_buffers: Vec> = pub_keys + .iter() + .map(|pk| ManagedBuffer::from(pk.serialize().unwrap())) + .collect(); + + ( + ManagedBuffer::new_from_bytes(signature.serialize().unwrap().as_slice()), + pk_buffers, + ) + } + + /// Calculates the number of signers based on the bitmap. + /// Each bit in the bitmap represents whether a validator signed. + pub fn calculate_signer_count(&self, bitmap: &ManagedBuffer) -> usize { + let bitmap_bytes = bitmap.to_vec(); + let signer_count: usize = bitmap_bytes + .iter() + .map(|byte| byte.count_ones() as usize) + .sum(); + signer_count.max(1) + } + + pub fn update_validator_key_in_chain_config( + &mut self, + validator_data: &ValidatorData, + pub_keys: &[ManagedBuffer], + ) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(CHAIN_CONFIG_ADDRESS) + .whitebox(chain_config::contract_obj, |sc| { + let new_pub_keys: ManagedVec< + VMHooksApi, + ManagedBuffer>, + > = pub_keys + .iter() + .map(|pk| ManagedBuffer::new_from_bytes(&pk.to_vec())) + .collect(); + + let validator_id = validator_data.id.to_u64().unwrap(); + let target_index = validator_id.saturating_sub(1) as usize; + + if target_index < new_pub_keys.len() { + let new_key = new_pub_keys.get(target_index).clone(); + sc.validator_info(&BigUint::from(validator_id)) + .update(|v| v.bls_key = new_key); + } + }); + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct BLSKey([u8; BLS_KEY_BYTE_LENGTH]); + +impl BLSKey { + pub fn random() -> ManagedBuffer { + let mut bytes = [0u8; BLS_KEY_BYTE_LENGTH]; + OsRng.fill_bytes(&mut bytes); + ManagedBuffer::new_from_bytes(&bytes) + } +} diff --git a/common/common-test-setup/src/base_setup/init.rs b/common/common-test-setup/src/base_setup/init.rs new file mode 100644 index 000000000..6d6834303 --- /dev/null +++ b/common/common-test-setup/src/base_setup/init.rs @@ -0,0 +1,144 @@ +use crate::constants::*; +use multiversx_sc::imports::OptionalValue; +use multiversx_sc_scenario::{ + api::StaticApi, + imports::{Address, BigUint, ManagedBuffer, MxscPath, TestTokenIdentifier, Vec}, + ScenarioWorld, +}; +use std::borrow::Cow; +use structs::aliases::TxNonce; + +pub struct BaseSetup { + pub world: ScenarioWorld, + operation_nonce: TxNonce, +} + +pub struct AccountSetup<'a> { + pub address: Address, + pub code_path: Option>, + pub esdt_balances: Option, u64, BigUint)>>, + pub egld_balance: Option>, +} + +#[derive(Clone, Debug)] +pub struct ExpectedLogs<'a> { + pub identifier: Cow<'a, str>, + pub topics: OptionalValue>>, + pub data: OptionalValue>, +} + +pub trait ErrorPayloadToString { + fn to_error_string(self) -> String; +} + +impl ErrorPayloadToString for ManagedBuffer { + fn to_error_string(self) -> String { + self.to_string() + } +} + +impl ErrorPayloadToString for Vec { + fn to_error_string(self) -> String { + ManagedBuffer::::new_from_bytes(&self).to_string() + } +} + +#[macro_export] +macro_rules! log { + ($identifier:expr) => { + ExpectedLogs { + identifier: ::std::borrow::Cow::<'_, str>::from($identifier), + topics: OptionalValue::None, + data: OptionalValue::None, + } + }; + ($identifier:expr, topics: [$($topic:expr),*]) => { + ExpectedLogs { + identifier: ::std::borrow::Cow::<'_, str>::from($identifier), + topics: OptionalValue::Some(vec![$(::std::borrow::Cow::<'_, str>::from($topic)),*]), + data: OptionalValue::None, + } + }; + ($identifier:expr, data: $data:expr) => { + ExpectedLogs { + identifier: ::std::borrow::Cow::<'_, str>::from($identifier), + topics: OptionalValue::None, + data: OptionalValue::Some(::std::borrow::Cow::<'_, str>::from($data)), + } + }; + ($identifier:expr, topics: [$($topic:expr),*], data: $data:expr) => { + ExpectedLogs { + identifier: ::std::borrow::Cow::<'_, str>::from($identifier), + topics: OptionalValue::Some(vec![$(::std::borrow::Cow::<'_, str>::from($topic)),*]), + data: match $data { + Some(data) => OptionalValue::Some(::std::borrow::Cow::<'_, str>::from(data)), + None => OptionalValue::None, + }, + } + }; +} + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.register_contract(FEE_MARKET_CODE_PATH, mvx_fee_market::ContractBuilder); + blockchain.register_contract(HEADER_VERIFIER_CODE_PATH, header_verifier::ContractBuilder); + blockchain.register_contract(CHAIN_CONFIG_CODE_PATH, chain_config::ContractBuilder); + blockchain.register_contract(TESTING_SC_CODE_PATH, testing_sc::ContractBuilder); + blockchain.register_contract(CHAIN_FACTORY_CODE_PATH, chain_factory::ContractBuilder); + blockchain.register_contract(SOVEREIGN_FORGE_CODE_PATH, sovereign_forge::ContractBuilder); + blockchain.register_contract(MVX_ESDT_SAFE_CODE_PATH, mvx_esdt_safe::ContractBuilder); + blockchain.register_contract(SOV_FEE_MARKET_CODE_PATH, sov_fee_market::ContractBuilder); + + blockchain +} + +impl BaseSetup { + pub fn new(account_setups: Vec) -> Self { + let mut world = world(); + + for acc in account_setups { + let mut acc_builder = match acc.code_path { + Some(code_path) => world.account(acc.address.clone()).code(code_path).nonce(1), + None => world.account(acc.address.clone()).nonce(1), + }; + + if let Some(esdt_balances) = &acc.esdt_balances { + for (token_id, nonce, amount) in esdt_balances { + acc_builder = if *nonce != 0 { + acc_builder.esdt_nft_balance( + *token_id, + *nonce, + amount.clone(), + ManagedBuffer::new(), + ) + } else { + acc_builder.esdt_balance(*token_id, amount.clone()) + }; + } + } + + if let Some(balance) = &acc.egld_balance { + acc_builder.balance(balance.clone()); + } + } + + Self { + world, + operation_nonce: 0, + } + } + + pub fn operation_nonce(&self) -> TxNonce { + self.operation_nonce + } + + pub fn next_operation_nonce(&mut self) -> TxNonce { + let nonce = self.operation_nonce; + self.operation_nonce = self + .operation_nonce + .checked_add(1) + .expect("operation nonce overflow"); + nonce + } +} diff --git a/common/common-test-setup/src/base_setup/log_validations.rs b/common/common-test-setup/src/base_setup/log_validations.rs new file mode 100644 index 000000000..102b67d48 --- /dev/null +++ b/common/common-test-setup/src/base_setup/log_validations.rs @@ -0,0 +1,167 @@ +use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; +use multiversx_sc::imports::OptionalValue; +use multiversx_sc_scenario::scenario_model::Log; +use std::borrow::Cow; + +use crate::{ + base_setup::init::ExpectedLogs, + constants::{ + EXECUTED_BRIDGE_OP_EVENT, EXECUTE_BRIDGE_OPS_ENDPOINT, EXECUTE_OPERATION_ENDPOINT, + INTERNAL_VM_ERRORS, + }, +}; + +pub fn assert_expected_logs(logs: Vec, expected_logs: Vec) { + for expected_log in expected_logs { + let identifier = expected_log.identifier.as_ref(); + let mut matching_logs: Vec<&Log> = logs + .iter() + .filter(|log| log.endpoint == identifier) + .collect(); + + assert!( + !matching_logs.is_empty(), + "Expected log '{}' not found. Logs: {:?}", + identifier, + logs + ); + + if let OptionalValue::Some(ref topics) = expected_log.topics { + matching_logs = validate_expected_topics(matching_logs, topics, identifier); + } + + if let OptionalValue::Some(ref data) = expected_log.data { + validate_expected_data(&matching_logs, data.as_ref(), identifier); + } else { + if identifier != EXECUTE_BRIDGE_OPS_ENDPOINT && identifier != EXECUTE_OPERATION_ENDPOINT + { + continue; + } + + if matches!(&expected_log.topics, OptionalValue::None) + || matches!(&expected_log.topics, OptionalValue::Some(topics) if topics[0] != EXECUTED_BRIDGE_OP_EVENT) + { + continue; + } + + assert!( + matching_logs.len() == 1, + "Expected exactly one log for event '{}', but found {}. Logs: {:?}", + identifier, + matching_logs.len(), + matching_logs + ); + + check_for_internal_vm_error_log(logs.clone(), &expected_log); + + let first_log = matching_logs.first().unwrap(); + let has_data = first_log.data.iter().any(|data| !data.is_empty()); + assert!( + !has_data, + "Expected no data (None or empty) for event '{}', but found one. Logs: {:?}", + identifier, first_log + ); + } + } +} + +fn validate_expected_topics<'a>( + logs: Vec<&'a Log>, + topics: &[Cow<'_, str>], + endpoint: &str, +) -> Vec<&'a Log> { + let expected_topics_bytes: Vec> = topics + .iter() + .map(|topic| topic.as_bytes().to_vec()) + .collect(); + + let filtered_logs: Vec<&Log> = logs + .into_iter() + .filter(|log| log_contains_all_topics(log, &expected_topics_bytes)) + .collect(); + + assert!( + !filtered_logs.is_empty(), + "Expected topics '{}' not found for event '{}'. Logs: {:?}", + topics + .iter() + .map(|topic| topic.as_ref()) + .collect::>() + .join(", "), + endpoint, + filtered_logs + ); + + filtered_logs +} + +fn validate_expected_data(logs: &[&Log], expected_data: &str, endpoint: &str) { + let expected_data_bytes = vec![expected_data.as_bytes().to_vec()]; + assert!( + logs.iter() + .any(|log| log_contains_expected_data(log, &expected_data_bytes)), + "Expected data '{}' not found for event '{}'. Logs: {:?}", + expected_data, + endpoint, + logs + ); +} + +fn check_for_internal_vm_error_log(expected_logs: Vec, expected_log: &ExpectedLogs) { + let internal_vm_logs: Vec = expected_logs + .into_iter() + .filter(|log| log.endpoint == INTERNAL_VM_ERRORS) + .collect(); + + assert!( + internal_vm_logs.is_empty(), + "Unexpected internal VM error log found while validating event '{}'. Logs: {:?}", + expected_log.identifier.as_ref(), + internal_vm_logs + ); +} + +fn log_contains_all_topics(log: &Log, expected_topics: &[Vec]) -> bool { + expected_topics.iter().all(|expected_topic| { + log.topics + .iter() + .any(|topic| topic_matches(topic, expected_topic)) + }) +} + +fn log_contains_expected_data(log: &Log, expected_data_bytes: &[Vec]) -> bool { + expected_data_bytes.iter().all(|expected_data| { + log.data + .iter() + .any(|log_data| data_matches(log_data, expected_data)) + }) +} + +fn topic_matches(log_topic: &[u8], expected_topic: &[u8]) -> bool { + if log_topic == expected_topic { + return true; + } + + BASE64_STANDARD + .decode(log_topic) + .map(|decoded| decoded == expected_topic) + .unwrap_or(false) +} + +fn data_matches(log_data: &[u8], expected_data: &[u8]) -> bool { + if log_data + .windows(expected_data.len()) + .any(|window| window == expected_data) + { + return true; + } + + BASE64_STANDARD + .decode(log_data) + .map(|decoded| { + decoded + .windows(expected_data.len()) + .any(|window| window == expected_data) + }) + .unwrap_or(false) +} diff --git a/common/common-test-setup/src/base_setup/mod.rs b/common/common-test-setup/src/base_setup/mod.rs new file mode 100644 index 000000000..7d3074c8a --- /dev/null +++ b/common/common-test-setup/src/base_setup/mod.rs @@ -0,0 +1,7 @@ +pub mod checks; +pub mod complete_setup_phase; +pub mod contract_endpoints; +pub mod deploy; +pub mod helpers; +pub mod init; +pub mod log_validations; diff --git a/common/common-test-setup/src/constants.rs b/common/common-test-setup/src/constants.rs index 3725ba675..553fe9dd6 100644 --- a/common/common-test-setup/src/constants.rs +++ b/common/common-test-setup/src/constants.rs @@ -1,19 +1,31 @@ -use multiversx_sc_scenario::imports::{MxscPath, TestAddress, TestSCAddress}; +use multiversx_sc_scenario::imports::{MxscPath, TestAddress, TestSCAddress, TestTokenIdentifier}; pub const ESDT_SAFE_ADDRESS: TestSCAddress = TestSCAddress::new("esdt-safe"); pub const FEE_MARKET_ADDRESS: TestSCAddress = TestSCAddress::new("fee-market"); +pub const SOV_FEE_MARKET_ADDRESS: TestSCAddress = TestSCAddress::new("sov-fee-market"); pub const HEADER_VERIFIER_ADDRESS: TestSCAddress = TestSCAddress::new("header-verifier"); - pub const CHAIN_CONFIG_ADDRESS: TestSCAddress = TestSCAddress::new("chain-config"); - pub const TESTING_SC_ADDRESS: TestSCAddress = TestSCAddress::new("testing-sc"); -pub const ENSHRINE_ADDRESS: TestAddress = TestAddress::new("enshrine"); +pub const CHAIN_FACTORY_SC_ADDRESS: TestSCAddress = TestSCAddress::new("chain-factory"); +pub const SOVEREIGN_FORGE_SC_ADDRESS: TestSCAddress = TestSCAddress::new("sovereign-forge"); pub const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); -pub const USER: TestAddress = TestAddress::new("user"); +pub const USER_ADDRESS: TestAddress = TestAddress::new("user"); +pub const INSUFFICIENT_WEGLD_ADDRESS: TestAddress = TestAddress::new("insufficient_wegld"); +pub const RECEIVER_ADDRESS: TestAddress = TestAddress::new("receiver"); +pub const TESTING_SC: &str = "Testing SC"; +pub const USER_ADDRESS_STR: &str = "User Address"; +pub const MVX_ESDT_SAFE_SHARD_0: &str = "MVX ESDT Safe Shard 0"; +pub const MVX_ESDT_SAFE_SHARD_1: &str = "MVX ESDT Safe Shard 1"; +pub const MVX_ESDT_SAFE_SHARD_2: &str = "MVX ESDT Safe Shard 2"; +pub const UNKNOWN_MVX_ESDT_SAFE: &str = "Unknown MVX ESDT Safe"; +pub const FEE_MARKET_SHARD_0: &str = "Fee Market Shard 0"; +pub const FEE_MARKET_SHARD_1: &str = "Fee Market Shard 1"; +pub const FEE_MARKET_SHARD_2: &str = "Fee Market Shard 2"; +pub const UNKNOWN_FEE_MARKET: &str = "Unknown Fee Market"; pub const FEE_MARKET_CODE_PATH: MxscPath = - MxscPath::new("../fee-market/output/fee-market.mxsc.json"); + MxscPath::new("../mvx-fee-market/output/mvx-fee-market.mxsc.json"); pub const HEADER_VERIFIER_CODE_PATH: MxscPath = MxscPath::new("../header-verifier/output/header-verifier.mxsc.json"); pub const CHAIN_CONFIG_CODE_PATH: MxscPath = @@ -24,18 +36,97 @@ pub const MVX_ESDT_SAFE_CODE_PATH: MxscPath = MxscPath::new("../mvx-esdt-safe/output/mvx-esdt-safe.mxsc.json"); pub const SOV_ESDT_SAFE_CODE_PATH: MxscPath = MxscPath::new("../sov-esdt-safe/output/to-sovereign.mxsc.json"); +pub const CHAIN_FACTORY_CODE_PATH: MxscPath = + MxscPath::new("../chain-factory/output/chain-factory.mxsc.json"); +pub const SOVEREIGN_FORGE_CODE_PATH: MxscPath = + MxscPath::new("../sovereign-forge/output/sovereign-forge.mxsc.json"); +pub const SOV_REGISTRAR_CODE_PATH: MxscPath = + MxscPath::new("../sov-registrar/output/sov-registrar.mxsc.json"); +pub const SOV_FEE_MARKET_CODE_PATH: MxscPath = + MxscPath::new("../sov-fee-market/output/sov-fee-market.mxsc.json"); -pub const FEE_TOKEN: &str = "INTERNS-eaad15"; -pub const FIRST_TEST_TOKEN: &str = "GREEN-0e161c"; -pub const SECOND_TEST_TOKEN: &str = "LTST-4f849e"; -pub const SOV_TOKEN: &str = "sov-GREEN-0e161c"; +pub const FEE_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("INTERNS-eaad15"); +pub const FIRST_TEST_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("GREEN-0e161c"); +pub const SECOND_TEST_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("LTST-4f849e"); +pub const NATIVE_TEST_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("NATIVE-123456"); +pub const SOV_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("sov-GREEN-0e161c"); +pub const SOV_FIRST_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("sov-ABC-ab3df1"); +pub const SOV_SECOND_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("sov-XYZ-1d3abc"); +pub const FIRST_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("ABC-123456"); +pub const SECOND_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("XYZ-678901"); pub const TOKEN_TICKER: &str = "GREEN"; +pub const TOKEN_DISPLAY_NAME: &str = "Sovereign"; +pub const REGISTER_TOKEN_PREFIX: &str = "sov-"; +pub const REGISTER_DEFAULT_TOKEN: &str = "SOV-123456"; +pub const NFT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("NFT-123456"); +pub const CROWD_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("CROWD-123456"); +pub const FUNGIBLE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("FUNG-123456"); +pub const PREFIX_NFT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("sov-NFT-123456"); +pub const WEGLD_IDENTIFIER: TestTokenIdentifier = TestTokenIdentifier::new("WEGLD-123456"); +pub const WRONG_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WRONG-TOKEN"); +pub const TRUSTED_TOKEN: &str = "USDC-c76f1f"; + +pub const SOVEREIGN_RECEIVER_ADDRESS: TestAddress = + TestAddress::new("erd18tudnj2z8vjh0339yu3vrkgzz2jpz8mjq0uhgnmklnap6z33qqeszq2yn4"); -pub const SOV_TO_MVX_TOKEN_STORAGE_KEY: &str = "sovToMxTokenId"; -pub const MVX_TO_SOV_TOKEN_STORAGE_KEY: &str = "mxToSovTokenId"; +pub const SOV_TO_MVX_TOKEN_STORAGE_KEY: &str = "sovToMvxTokenId"; +pub const NATIVE_TOKEN_STORAGE_KEY: &str = "nativeToken"; +pub const MVX_TO_SOV_TOKEN_STORAGE_KEY: &str = "mvxToSovTokenId"; pub const OPERATION_HASH_STATUS_STORAGE_KEY: &str = "operationHashStatus"; +pub const SOVEREIGN_TOKEN_PREFIX: &str = "sov"; +pub const CHAIN_ID: &str = "svch"; +pub const INTERACTOR_WORKING_DIR: &str = "interactor"; +pub const WRONG_ENDPOINT_NAME: &str = "WRONG-ENDPOINT-NAME"; +pub const ESDT_SAFE_CONFIG_STORAGE_KEY: &str = "crossChainConfig"; +pub const TOKEN_FEE_STORAGE_KEY: &str = "tokenFee"; +pub const NUMBER_OF_SHARDS: u32 = 3; +pub const PREFERRED_CHAIN_IDS: [&str; 3] = ["shd0", "shd1", "shd2"]; +pub const SHARD_0: u32 = 0; +pub const SHARD_1: u32 = 1; +pub const SHARD_2: u32 = 2; +pub const PAUSE_CONTRACT_LOG: &str = "pauseContract"; +pub const UNPAUSE_CONTRACT_LOG: &str = "unpauseContract"; +pub const TESTING_SC_ENDPOINT: &str = "hello"; +pub const READ_NATIVE_TOKEN_TESTING_SC_ENDPOINT: &str = "read_native_token"; +pub const STATE_FILE: &str = "state.toml"; +pub const NATIVE_TOKEN_TICKER: &str = "SOV"; +pub const NATIVE_TOKEN_NAME: &str = "SovereignToken"; +pub const TRUSTED_TOKEN_NAME: &str = "TRUSTED"; +pub const INTERNAL_VM_ERRORS: &str = "internalVMErrors"; pub const ISSUE_COST: u64 = 50_000_000_000_000_000; // 0.05 EGLD +pub const GAS_LIMIT: u64 = 90_000_000; // 90 million gas limit pub const ONE_HUNDRED_MILLION: u32 = 100_000_000; pub const ONE_HUNDRED_THOUSAND: u32 = 100_000; pub const OWNER_BALANCE: u128 = 100_000_000_000_000_000_000_000; +pub const DEPLOY_COST: u64 = 100_000; +pub const ESDT_SAFE_BALANCE: u128 = 100_000_000_000_000_000; +pub const ONE_THOUSAND_TOKENS: u128 = 1_000_000_000_000_000_000_000u128; +pub const ONE_HUNDRED_TOKENS: u128 = 100_000_000_000_000_000_000u128; +pub const TEN_TOKENS: u128 = 10_000_000_000_000_000_000u128; +pub const PER_TRANSFER: u64 = 100; +pub const PER_GAS: u64 = 1; +pub const EGLD_0_05: u64 = 50_000_000_000_000_000; +pub const NUM_TOKENS_TO_MINT: u64 = 2; + +pub const EXECUTED_BRIDGE_OP_EVENT: &str = "executedBridgeOp"; +pub const DEPOSIT_EVENT: &str = "deposit"; +pub const SC_CALL_EVENT: &str = "scCall"; +pub const REGISTER_TOKEN_ENDPOINT: &str = "registerToken"; +pub const EXECUTE_OPERATION_ENDPOINT: &str = "execute"; +pub const EXECUTE_BRIDGE_OPS_ENDPOINT: &str = "executeBridgeOps"; +pub const REGISTER_TOKEN_EVENT: &str = "register_token"; +pub const CHANGE_VALIDATOR_SET_ENDPOINT: &str = "changeValidatorSet"; +pub const UPDATE_ESDT_SAFE_CONFIG_ENDPOINT: &str = "updateEsdtSafeConfig"; +pub const SET_FEE_ENDPOINT: &str = "setFee"; +pub const REMOVE_FEE_ENDPOINT: &str = "removeFee"; +pub const DISTRIBUTE_FEES_ENDPOINT: &str = "distributeFees"; +pub const COMPLETE_SETUP_PHASE_ENDPOINT: &str = "completeSetupPhase"; +pub const REGISTER_BLS_KEY_ENDPOINT: &str = "registerBlsKey"; +pub const UNREGISTER_BLS_KEY_ENDPOINT: &str = "unregisterBlsKey"; +pub const UPDATE_SOVEREIGN_CONFIG_ENDPOINT: &str = "updateSovereignConfig"; +pub const MULTI_ESDT_NFT_TRANSFER_EVENT: &str = "MultiESDTNFTTransfer"; +pub const TRANSFER_VALUE_ONLY_LOG: &str = "transferValueOnly"; + +pub const WALLET_SHARD_0: &str = "wallets/wallet_shard_0.pem"; +pub const FAILED_TO_LOAD_WALLET_SHARD_0: &str = "Failed to load wallet for shard 0"; diff --git a/common/common-test-setup/src/lib.rs b/common/common-test-setup/src/lib.rs index 0770194c8..1f03fe36d 100644 --- a/common/common-test-setup/src/lib.rs +++ b/common/common-test-setup/src/lib.rs @@ -1,262 +1,2 @@ -#![no_std] +pub mod base_setup; pub mod constants; -use constants::{ - CHAIN_CONFIG_ADDRESS, CHAIN_CONFIG_CODE_PATH, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, - FEE_MARKET_CODE_PATH, HEADER_VERIFIER_ADDRESS, HEADER_VERIFIER_CODE_PATH, OWNER_ADDRESS, - TESTING_SC_ADDRESS, TESTING_SC_CODE_PATH, -}; -use cross_chain::storage::CrossChainStorage; -use header_verifier::{Headerverifier, OperationHashStatus}; -use multiversx_sc_scenario::{ - api::StaticApi, - imports::{ - BigUint, ContractBase, EgldOrEsdtTokenIdentifier, EsdtTokenType, ManagedAddress, - ManagedBuffer, MultiValue2, MultiValue3, MultiValueEncoded, TestAddress, - TestTokenIdentifier, TokenIdentifier, Vec, - }, - multiversx_chain_vm::crypto_functions::sha256, - scenario_model::{Log, TxResponseStatus}, - DebugApi, ScenarioTxRun, ScenarioTxWhitebox, ScenarioWorld, -}; -use mvx_esdt_safe::bridging_mechanism::BridgingMechanism; -use proxies::{ - chain_config_proxy::ChainConfigContractProxy, - fee_market_proxy::{FeeMarketProxy, FeeStruct}, - header_verifier_proxy::HeaderverifierProxy, - testing_sc_proxy::TestingScProxy, -}; -use structs::configs::SovereignConfig; - -pub struct RegisterTokenArgs<'a> { - pub sov_token_id: TokenIdentifier, - pub token_type: EsdtTokenType, - pub token_display_name: &'a str, - pub token_ticker: &'a str, - pub num_decimals: usize, -} - -pub struct BaseSetup { - pub world: ScenarioWorld, -} - -pub struct AccountSetup<'a> { - pub address: TestAddress<'a>, - pub esdt_balances: Option, BigUint)>>, - pub egld_balance: Option>, -} - -fn world() -> ScenarioWorld { - let mut blockchain = ScenarioWorld::new(); - - blockchain.register_contract(FEE_MARKET_CODE_PATH, fee_market::ContractBuilder); - blockchain.register_contract(HEADER_VERIFIER_CODE_PATH, header_verifier::ContractBuilder); - blockchain.register_contract(CHAIN_CONFIG_CODE_PATH, chain_config::ContractBuilder); - blockchain.register_contract(TESTING_SC_CODE_PATH, testing_sc::ContractBuilder); - - blockchain -} - -impl BaseSetup { - #[allow(clippy::new_without_default)] - pub fn new(account_setups: Vec) -> Self { - let mut world = world(); - - for acc in account_setups { - let mut acc_builder = world.account(acc.address).nonce(1); - - if let Some(esdt_balances) = acc.esdt_balances { - for (token_id, amount) in esdt_balances { - acc_builder = acc_builder.esdt_balance(token_id, amount); - } - } - - if let Some(balance) = acc.egld_balance { - acc_builder.balance(balance); - } - } - - Self { world } - } - - pub fn deploy_fee_market(&mut self, fee: Option>) -> &mut Self { - self.world - .tx() - .from(OWNER_ADDRESS) - .typed(FeeMarketProxy) - .init(ESDT_SAFE_ADDRESS, fee) - .code(FEE_MARKET_CODE_PATH) - .new_address(FEE_MARKET_ADDRESS) - .run(); - - self - } - - pub fn deploy_testing_sc(&mut self) -> &mut Self { - self.world - .tx() - .from(OWNER_ADDRESS) - .typed(TestingScProxy) - .init() - .code(TESTING_SC_CODE_PATH) - .new_address(TESTING_SC_ADDRESS) - .run(); - - self - } - - pub fn deploy_header_verifier(&mut self) -> &mut Self { - self.world - .tx() - .from(OWNER_ADDRESS) - .typed(HeaderverifierProxy) - .init() - .code(HEADER_VERIFIER_CODE_PATH) - .new_address(HEADER_VERIFIER_ADDRESS) - .run(); - - self - } - - pub fn deploy_chain_config(&mut self, config: SovereignConfig) -> &mut Self { - let mut additional_stake_as_tuple = MultiValueEncoded::new(); - if let Some(additional_stake) = config.opt_additional_stake_required { - for stake in additional_stake { - additional_stake_as_tuple.push(MultiValue2::from((stake.token_id, stake.amount))); - } - } - - self.world - .tx() - .from(OWNER_ADDRESS) - .typed(ChainConfigContractProxy) - .init( - config.min_validators as usize, - config.max_validators as usize, - config.min_stake, - OWNER_ADDRESS, - additional_stake_as_tuple, - ) - .code(CHAIN_CONFIG_CODE_PATH) - .new_address(CHAIN_CONFIG_ADDRESS) - .run(); - - self - } - - pub fn assert_expected_error_message( - &mut self, - response: Result<(), TxResponseStatus>, - expected_error_message: Option<&str>, - ) { - match response { - Ok(_) => assert!( - expected_error_message.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => { - assert_eq!(expected_error_message, Some(error.message.as_str())) - } - } - } - - // TODO: Add a better check balance for esdt function after check storage is fixed - pub fn check_sc_esdt_balance( - &mut self, - tokens: Vec>, - contract_address: ManagedAddress, - contract: fn() -> ContractObj, - ) where - ContractObj: ContractBase + 'static, - { - self.world - .tx() - .from(OWNER_ADDRESS) - .to(contract_address) - .whitebox(contract, |sc: ContractObj| { - for token in tokens { - let (token_id, nonce, amount) = token.into_tuple(); - let balance = sc - .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(token_id), nonce); - assert_eq!(balance, BigUint::from(amount)); - } - }); - } - - pub fn check_deposited_tokens_amount(&mut self, tokens: Vec<(TestTokenIdentifier, u64)>) { - self.world - .tx() - .from(OWNER_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .whitebox(mvx_esdt_safe::contract_obj, |sc| { - for token in tokens { - let (token_id, amount) = token; - assert!(sc.deposited_tokens_amount(&token_id.into()).get() == amount); - } - }); - } - - pub fn check_multiversx_to_sovereign_token_id_mapper_is_empty(&mut self, token_name: &str) { - self.world - .query() - .to(ESDT_SAFE_ADDRESS) - .whitebox(mvx_esdt_safe::contract_obj, |sc| { - assert!(sc - .multiversx_to_sovereign_token_id_mapper( - &TestTokenIdentifier::new(token_name).into() - ) - .is_empty()); - }) - } - - pub fn check_operation_hash_status_is_empty( - &mut self, - operation_hash: &ManagedBuffer, - ) { - self.world.query().to(HEADER_VERIFIER_ADDRESS).whitebox( - header_verifier::contract_obj, - |sc| { - let operation_hash_whitebox = - ManagedBuffer::new_from_bytes(&operation_hash.to_vec()); - let hash_of_hashes = - ManagedBuffer::new_from_bytes(&sha256(&operation_hash_whitebox.to_vec())); - - assert!(sc - .operation_hash_status(&hash_of_hashes, &operation_hash_whitebox) - .is_empty()); - }, - ) - } - - pub fn check_operation_hash_status( - &mut self, - operation_hash: &ManagedBuffer, - status: OperationHashStatus, - ) { - self.world.query().to(HEADER_VERIFIER_ADDRESS).whitebox( - header_verifier::contract_obj, - |sc| { - let operation_hash_whitebox = - ManagedBuffer::new_from_bytes(&operation_hash.to_vec()); - let hash_of_hashes = - ManagedBuffer::new_from_bytes(&sha256(&operation_hash_whitebox.to_vec())); - - assert!( - sc.operation_hash_status(&hash_of_hashes, &operation_hash_whitebox) - .get() - == status - ); - }, - ) - } - - pub fn assert_expected_log(&mut self, logs: Vec, expected_log: &str) { - let expected_bytes = ManagedBuffer::::from(expected_log).to_vec(); - - let found_log = logs - .iter() - .find(|log| log.topics.iter().any(|topic| *topic == expected_bytes)); - - assert!(found_log.is_some(), "Expected log not found"); - } -} diff --git a/common/utils/Cargo.toml b/common/common-utils/Cargo.toml similarity index 64% rename from common/utils/Cargo.toml rename to common/common-utils/Cargo.toml index 4e16cc278..cf8635353 100644 --- a/common/utils/Cargo.toml +++ b/common/common-utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "utils" +name = "common-utils" version = "0.1.0" authors = ["Dorin Iancu "] edition = "2021" @@ -13,6 +13,11 @@ path = "../structs" [dependencies.error-messages] path = "../error-messages" +[dependencies.proxies] +path = "../proxies" + +[dependencies.custom-events] +path = "../custom-events" + [dependencies.multiversx-sc] -version = "=0.57.1" -features = ["esdt-token-payment-legacy-decode"] +version = "0.63.0" diff --git a/common/common-utils/src/lib.rs b/common/common-utils/src/lib.rs new file mode 100644 index 000000000..9393171b5 --- /dev/null +++ b/common/common-utils/src/lib.rs @@ -0,0 +1,184 @@ +#![no_std] + +use error_messages::{ + CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC, ERROR_AT_GENERATING_OPERATION_HASH, ERR_EMPTY_PAYMENTS, + INVALID_CHAIN_ID, INVALID_SC_ADDRESS, TOKEN_ID_NO_PREFIX, +}; +use proxies::header_verifier_proxy::HeaderverifierProxy; +use structs::aliases::PaymentsVec; + +multiversx_sc::imports!(); + +const DASH: u8 = b'-'; +const MAX_TOKEN_ID_LEN: usize = 32; +const MIN_PREFIX_LENGTH: usize = 1; +const MAX_PREFIX_LENGTH: usize = 4; +const CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz"; + +#[multiversx_sc::module] +pub trait CommonUtilsModule: custom_events::CustomEventsModule { + fn lock_operation_hash_wrapper( + &self, + hash_of_hashes: &ManagedBuffer, + hash: &ManagedBuffer, + nonce: u64, + ) -> Option { + self.tx() + .to(self.blockchain().get_owner_address()) + .typed(HeaderverifierProxy) + .lock_operation_hash(hash_of_hashes, hash, nonce) + .returns(ReturnsResult) + .sync_call() + .into_option() + } + + fn remove_executed_hash_wrapper( + &self, + hash_of_hashes: &ManagedBuffer, + op_hash: &ManagedBuffer, + ) -> Option { + self.tx() + .to(self.blockchain().get_owner_address()) + .typed(HeaderverifierProxy) + .remove_executed_hash(hash_of_hashes, op_hash) + .returns(ReturnsResult) + .sync_call() + .into_option() + } + + fn complete_operation( + &self, + hash_of_hashes: &ManagedBuffer, + operation_hash: &ManagedBuffer, + error_message: Option, + ) { + let remove_error = self.remove_executed_hash_wrapper(hash_of_hashes, operation_hash); + + let merged_error = match (error_message, remove_error) { + (None, None) => None, + (Some(err), None) | (None, Some(err)) => Some(err), + (Some(err1), Some(err2)) => { + let mut errors: ManagedVec = ManagedVec::new(); + errors.push(err1); + errors.push(err2); + Some(self.combine_error_messages(&errors)) + } + }; + + self.execute_bridge_operation_event(hash_of_hashes, operation_hash, merged_error); + } + + fn require_sc_address(&self, address: &ManagedAddress) { + require!( + !address.is_zero() && self.blockchain().is_smart_contract(address), + INVALID_SC_ADDRESS + ); + } + + fn is_valid_token_id(&self, token_id: &EgldOrEsdtTokenIdentifier) -> bool { + token_id.clone().is_valid() + } + + fn pop_first_payment( + &self, + payments: PaymentsVec, + ) -> MultiValue2>, PaymentsVec> { + require!(!payments.is_empty(), ERR_EMPTY_PAYMENTS); + + let mut new_payments = payments; + + let first_payment = new_payments.get(0).clone(); + new_payments.remove(0); + + MultiValue2::from((OptionalValue::Some(first_payment.clone()), new_payments)) + } + + fn has_prefix(&self, token_id: &EgldOrEsdtTokenIdentifier) -> bool { + let buffer = token_id.as_managed_buffer(); + let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; + let slice = buffer.load_to_byte_array(&mut array_buffer); + + let counter = slice.iter().filter(|&&c| c == DASH).count(); + + if counter == 2 { + return true; + } + + false + } + + #[inline] + fn require_token_has_prefix(&self, token_id: &EgldOrEsdtTokenIdentifier) { + require!(self.has_prefix(token_id), TOKEN_ID_NO_PREFIX); + } + + fn has_sov_prefix( + &self, + token_id: &EgldOrEsdtTokenIdentifier, + chain_prefix: &ManagedBuffer, + ) -> bool { + if !self.has_prefix(token_id) { + return false; + } + + let buffer = token_id.as_managed_buffer(); + let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; + let slice = buffer.load_to_byte_array(&mut array_buffer); + + if let Some(index) = slice.iter().position(|&b| b == DASH) { + let prefix = ManagedBuffer::from(&slice[..index]); + + if prefix == chain_prefix.clone() { + return true; + } + } + + false + } + + fn validate_operation_hash(&self, hash: &ManagedBuffer) -> Option { + if hash.is_empty() { + return Some(ERROR_AT_GENERATING_OPERATION_HASH.into()); + } + + None + } + + fn is_chain_id_lowercase_alphanumeric(&self, chain_id: &ManagedBuffer) -> bool { + let mut chain_id_byte_array = [0u8; 4]; + let chain_id_byte_array = chain_id.load_to_byte_array(&mut chain_id_byte_array); + + chain_id_byte_array.iter().all(|b| CHARSET.contains(b)) + } + + fn combine_error_messages( + &self, + errors: &ManagedVec, + ) -> ManagedBuffer { + let newline: ManagedBuffer = "\n".into(); + let mut aggregated = ManagedBuffer::new(); + + for (index, error_message) in errors.iter().enumerate() { + if index > 0 { + aggregated.append(&newline); + } + aggregated.append(&error_message); + } + + aggregated + } + + #[inline] + fn validate_chain_id(&self, chain_id: &ManagedBuffer) { + let id_length = chain_id.len(); + require!( + (MIN_PREFIX_LENGTH..=MAX_PREFIX_LENGTH).contains(&id_length), + INVALID_CHAIN_ID + ); + + require!( + self.is_chain_id_lowercase_alphanumeric(chain_id), + CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC + ); + } +} diff --git a/common/cross-chain/Cargo.toml b/common/cross-chain/Cargo.toml index ef678d0d9..cdec38a4e 100644 --- a/common/cross-chain/Cargo.toml +++ b/common/cross-chain/Cargo.toml @@ -8,10 +8,10 @@ edition = "2021" path = "src/lib.rs" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dependencies.multiversx-sc-modules] -version = "=0.57.1" +version = "0.63.0" [dependencies.structs] path = "../structs" @@ -19,8 +19,11 @@ path = "../structs" [dependencies.proxies] path = "../proxies" -[dependencies.utils] -path = "../utils" +[dependencies.common-utils] +path = "../common-utils" [dependencies.error-messages] path = "../error-messages" + +[dependencies.custom-events] +path = "../custom-events" diff --git a/common/cross-chain/src/deposit_common.rs b/common/cross-chain/src/deposit_common.rs index d3ca584f7..d6c50b119 100644 --- a/common/cross-chain/src/deposit_common.rs +++ b/common/cross-chain/src/deposit_common.rs @@ -1,11 +1,14 @@ use error_messages::{ - BANNED_ENDPOINT_NAME, DEPOSIT_OVER_MAX_AMOUNT, GAS_LIMIT_TOO_HIGH, NOTHING_TO_TRANSFER, - TOKEN_ALREADY_REGISTERED, TOKEN_BLACKLISTED, TOO_MANY_TOKENS, + BANNED_ENDPOINT_NAME, CALLER_IS_BLACKLISTED, DEPOSIT_OVER_MAX_AMOUNT, ESDT_SAFE_STILL_PAUSED, + GAS_LIMIT_TOO_HIGH, NOTHING_TO_TRANSFER, TOKEN_BLACKLISTED, TOO_MANY_TOKENS, }; -use proxies::fee_market_proxy::FeeMarketProxy; +use multiversx_sc::api::ESDT_LOCAL_BURN_FUNC_NAME; +use proxies::mvx_fee_market_proxy::MvxFeeMarketProxy; use structs::{ - aliases::{EventPaymentTuple, ExtractedFeeResult, GasLimit, TxNonce}, - operation::TransferData, + aliases::{ + EventPaymentTuple, ExtractedFeeResult, GasLimit, OptionalValueTransferDataTuple, TxNonce, + }, + operation::{OperationData, TransferData}, }; use crate::MAX_TRANSFERS_PER_TX; @@ -14,12 +17,81 @@ multiversx_sc::imports!(); #[multiversx_sc::module] pub trait DepositCommonModule: - crate::storage::CrossChainStorage + crate::execute_common::ExecuteCommonModule + utils::UtilsModule + crate::storage::CrossChainStorage + + crate::execute_common::ExecuteCommonModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + multiversx_sc_modules::pause::PauseModule { + fn deposit_common( + &self, + to: ManagedAddress, + opt_transfer_data: OptionalValueTransferDataTuple, + process_payment: F, + ) where + F: Fn(&EgldOrEsdtTokenPayment) -> EventPaymentTuple, + { + require!(self.not_paused(), ESDT_SAFE_STILL_PAUSED); + self.require_caller_not_blacklisted(); + + let option_transfer_data = TransferData::from_optional_value(opt_transfer_data.clone()); + + if let Some(transfer_data) = option_transfer_data.as_ref() { + self.require_gas_limit_under_limit(transfer_data.gas_limit); + self.require_endpoint_not_banned(&transfer_data.function); + } + + let (fees_payment, payments) = self + .check_and_extract_fee(opt_transfer_data.is_some()) + .into_tuple(); + + let mut total_tokens_for_fees = 0usize; + let mut event_payments = MultiValueEncoded::new(); + let mut refundable_payments = ManagedVec::::new(); + + for payment in &payments { + let token_identifier = payment.token_identifier.clone(); + self.require_below_max_amount(&token_identifier, &payment.amount); + self.require_token_not_on_blacklist(&token_identifier); + + if !self.is_token_whitelist_empty() && !self.is_token_whitelisted(&token_identifier) { + refundable_payments.push(payment.clone()); + continue; + } + total_tokens_for_fees += 1; + + let processed_payment = process_payment(&payment); + + event_payments.push(processed_payment); + } + + self.match_fee_payment(total_tokens_for_fees, &fees_payment, &option_transfer_data); + + let caller = self.blockchain().get_caller(); + self.refund_tokens(&caller, refundable_payments); + + let tx_nonce = self.get_current_and_increment_tx_nonce(); + + if payments.is_empty() { + self.sc_call_event( + &to, + OperationData::new(tx_nonce, caller, option_transfer_data), + ); + + return; + } + + self.deposit_event( + &to, + &event_payments, + OperationData::new(tx_nonce, caller, option_transfer_data), + ); + } + fn match_fee_payment( &self, total_tokens_for_fees: usize, - fees_payment: &OptionalValue>, + fees_payment: &OptionalValue>, opt_transfer_data: &Option::Api>>, ) { match fees_payment { @@ -35,7 +107,7 @@ pub trait DepositCommonModule: self.tx() .to(fee_market_address) - .typed(FeeMarketProxy) + .typed(MvxFeeMarketProxy) .subtract_fee(caller, total_tokens_for_fees, OptionalValue::Some(gas)) .payment(fee.clone()) .sync_call(); @@ -45,7 +117,7 @@ pub trait DepositCommonModule: } fn check_and_extract_fee(&self, has_transfer_data: bool) -> ExtractedFeeResult { - let payments = self.call_value().all_esdt_transfers().clone(); + let payments = self.call_value().all_transfers().clone(); require!(payments.len() <= MAX_TRANSFERS_PER_TX, TOO_MANY_TOKENS); let fee_enabled = self @@ -63,28 +135,30 @@ pub trait DepositCommonModule: } } - fn burn_sovereign_token(&self, payment: &EsdtTokenPayment) { + fn burn_sovereign_token(&self, payment: &EgldOrEsdtTokenPayment) { self.tx() .to(ToSelf) - .typed(system_proxy::UserBuiltinProxy) - .esdt_local_burn( - &payment.token_identifier, - payment.token_nonce, - &payment.amount, - ) + .raw_call(ESDT_LOCAL_BURN_FUNC_NAME) + .argument(&payment.token_identifier.as_managed_buffer()) + .argument(&payment.amount) .sync_call(); } fn get_event_payment_token_data( &self, current_sc_address: &ManagedAddress, - payment: &EsdtTokenPayment, + payment: &EgldOrEsdtTokenPayment, ) -> EventPaymentTuple { - let mut current_token_data = self.blockchain().get_esdt_token_data( - current_sc_address, - &payment.token_identifier, - payment.token_nonce, - ); + let token_identifier = payment.token_identifier.clone(); + let mut current_token_data = if payment.token_identifier.is_egld() { + EsdtTokenData::default() + } else { + self.blockchain().get_esdt_token_data( + current_sc_address, + &token_identifier.clone().unwrap_esdt(), + payment.token_nonce, + ) + }; current_token_data.amount = payment.amount.clone(); MultiValue3::from(( @@ -94,16 +168,15 @@ pub trait DepositCommonModule: )) } - fn is_above_max_amount(&self, token_id: &TokenIdentifier, amount: &BigUint) -> bool { - let max_amount = self.max_bridged_amount(token_id).get(); - if max_amount > 0 { - amount > &max_amount - } else { - false - } + fn is_above_max_amount(&self, token_id: &EgldOrEsdtTokenIdentifier, amount: &BigUint) -> bool { + self.esdt_safe_config() + .get() + .max_bridged_token_amounts + .iter() + .any(|m| m.token_id == *token_id && amount > &m.amount) } - fn require_below_max_amount(&self, token_id: &TokenIdentifier, amount: &BigUint) { + fn require_below_max_amount(&self, token_id: &EgldOrEsdtTokenIdentifier, amount: &BigUint) { require!( !self.is_above_max_amount(token_id, amount), DEPOSIT_OVER_MAX_AMOUNT @@ -114,46 +187,38 @@ pub trait DepositCommonModule: fn refund_tokens( &self, caller: &ManagedAddress, - refundable_payments: ManagedVec>, + refundable_payments: ManagedVec>, ) { self.tx() .to(caller) - .multi_esdt(refundable_payments) + .payment(refundable_payments) .transfer_if_not_empty(); } fn burn_mainchain_token( &self, - payment: EsdtTokenPayment, + token_id: &EgldOrEsdtTokenIdentifier, + token_nonce: u64, + amount: &BigUint, payment_token_type: &EsdtTokenType, - sov_token_id: &TokenIdentifier, + sov_token_id: &EgldOrEsdtTokenIdentifier, ) -> u64 { self.tx() .to(ToSelf) .typed(system_proxy::UserBuiltinProxy) - .esdt_local_burn( - &payment.token_identifier, - payment.token_nonce, - &payment.amount, - ) + .esdt_local_burn(token_id.clone().unwrap_esdt(), token_nonce, amount) .sync_call(); let mut sov_token_nonce = 0; - if payment.token_nonce > 0 { + if token_nonce > 0 { sov_token_nonce = self - .multiversx_to_sovereign_esdt_info_mapper( - &payment.token_identifier, - payment.token_nonce, - ) + .multiversx_to_sovereign_esdt_info_mapper(token_id, token_nonce) .get() .token_nonce; if self.is_nft(payment_token_type) { - self.clear_mvx_to_sov_esdt_info_mapper( - &payment.token_identifier, - payment.token_nonce, - ); + self.clear_mvx_to_sov_esdt_info_mapper(token_id, token_nonce); self.clear_sov_to_mvx_esdt_info_mapper(sov_token_id, sov_token_nonce); } @@ -163,35 +228,42 @@ pub trait DepositCommonModule: } #[inline] - fn clear_mvx_to_sov_esdt_info_mapper(&self, id: &TokenIdentifier, nonce: u64) { + fn clear_mvx_to_sov_esdt_info_mapper( + &self, + id: &EgldOrEsdtTokenIdentifier, + nonce: u64, + ) { self.multiversx_to_sovereign_esdt_info_mapper(id, nonce) .take(); } #[inline] - fn clear_sov_to_mvx_esdt_info_mapper(&self, id: &TokenIdentifier, nonce: u64) { + fn clear_sov_to_mvx_esdt_info_mapper( + &self, + id: &EgldOrEsdtTokenIdentifier, + nonce: u64, + ) { self.sovereign_to_multiversx_esdt_info_mapper(id, nonce) .take(); } #[inline] - fn get_and_save_next_tx_id(&self) -> TxNonce { - self.last_tx_nonce().update(|last_tx_nonce| { - *last_tx_nonce += 1; - *last_tx_nonce - }) + fn get_current_and_increment_tx_nonce(&self) -> TxNonce { + let last_tx_nonce_mapper = self.last_tx_nonce(); + let current_nonce = last_tx_nonce_mapper.get(); + let next_nonce = current_nonce + 1; + last_tx_nonce_mapper.set(next_nonce); + + current_nonce } #[inline] - fn require_sov_token_id_not_registered(&self, id: &TokenIdentifier) { - require!( - self.sovereign_to_multiversx_token_id_mapper(id).is_empty(), - TOKEN_ALREADY_REGISTERED - ); + fn is_sov_token_id_registered(&self, id: &EgldOrEsdtTokenIdentifier) -> bool { + !self.sovereign_to_multiversx_token_id_mapper(id).is_empty() } #[inline] - fn require_token_not_on_blacklist(&self, token_id: &TokenIdentifier) { + fn require_token_not_on_blacklist(&self, token_id: &EgldOrEsdtTokenIdentifier) { require!( !self .esdt_safe_config() @@ -220,7 +292,7 @@ pub trait DepositCommonModule: } #[inline] - fn is_token_whitelisted(&self, token_id: &TokenIdentifier) -> bool { + fn is_token_whitelisted(&self, token_id: &EgldOrEsdtTokenIdentifier) -> bool { self.esdt_safe_config() .get() .token_whitelist @@ -234,4 +306,17 @@ pub trait DepositCommonModule: GAS_LIMIT_TOO_HIGH ); } + + #[inline] + fn require_caller_not_blacklisted(&self) { + let caller = self.blockchain().get_caller(); + require!( + !self + .esdt_safe_config() + .get() + .address_blacklist + .contains(&caller), + CALLER_IS_BLACKLISTED + ); + } } diff --git a/common/cross-chain/src/events.rs b/common/cross-chain/src/events.rs deleted file mode 100644 index 64c056a28..000000000 --- a/common/cross-chain/src/events.rs +++ /dev/null @@ -1,29 +0,0 @@ -use structs::{aliases::EventPaymentTuple, operation::OperationData}; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[multiversx_sc::module] -pub trait EventsModule { - #[event("deposit")] - fn deposit_event( - &self, - #[indexed] dest_address: &ManagedAddress, - #[indexed] tokens: &MultiValueEncoded>, - event_data: OperationData, - ); - - #[event("scCall")] - fn sc_call_event( - &self, - #[indexed] dest_address: &ManagedAddress, - event_data: OperationData, - ); - - #[event("executedBridgeOp")] - fn execute_bridge_operation_event( - &self, - #[indexed] hash_of_hashes: &ManagedBuffer, - #[indexed] hash_of_bridge_op: &ManagedBuffer, - ); -} diff --git a/common/cross-chain/src/execute_common.rs b/common/cross-chain/src/execute_common.rs index bc2baae9d..0ff9bb413 100644 --- a/common/cross-chain/src/execute_common.rs +++ b/common/cross-chain/src/execute_common.rs @@ -1,76 +1,87 @@ -use error_messages::NO_HEADER_VERIFIER_ADDRESS; -use proxies::header_verifier_proxy::HeaderverifierProxy; -use structs::operation::Operation; +use error_messages::{BURN_ESDT_FAILED, MINT_ESDT_FAILED}; multiversx_sc::imports!(); #[multiversx_sc::module] pub trait ExecuteCommonModule: crate::storage::CrossChainStorage { - fn calculate_operation_hash(&self, operation: &Operation) -> ManagedBuffer { - let mut serialized_data = ManagedBuffer::new(); + fn is_native_token(&self, token_identifier: &EgldOrEsdtTokenIdentifier) -> bool { + let esdt_safe_native_token_mapper = self.native_token(); - if let core::result::Result::Err(err) = operation.top_encode(&mut serialized_data) { - sc_panic!("Transfer data encode error: {}", err.message_bytes()); + if esdt_safe_native_token_mapper.is_empty() { + return false; } - let sha256 = self.crypto().sha256(&serialized_data); - let hash = sha256.as_managed_buffer().clone(); - - hash - } - - fn lock_operation_hash(&self, operation_hash: &ManagedBuffer, hash_of_hashes: &ManagedBuffer) { - self.tx() - .to(self.get_header_verifier_address()) - .typed(HeaderverifierProxy) - .lock_operation_hash(hash_of_hashes, operation_hash) - .sync_call(); + token_identifier == &esdt_safe_native_token_mapper.get() } - fn remove_executed_hash(&self, hash_of_hashes: &ManagedBuffer, op_hash: &ManagedBuffer) { - self.tx() - .to(self.get_header_verifier_address()) - .typed(HeaderverifierProxy) - .remove_executed_hash(hash_of_hashes, op_hash) - .sync_call(); + #[inline] + fn format_error( + &self, + error: &str, + token_id: EsdtTokenIdentifier, + error_code: u32, + ) -> ManagedBuffer { + let prefix: ManagedBuffer = error.into(); + let error_message = sc_format!("{} {}; error code: {}", prefix, token_id, error_code); + + error_message } - fn get_header_verifier_address(&self) -> ManagedAddress { - let header_verifier_address_mapper = self.header_verifier_address(); - - require!( - !header_verifier_address_mapper.is_empty(), - NO_HEADER_VERIFIER_ADDRESS - ); - - header_verifier_address_mapper.get() + #[inline] + fn try_esdt_local_burn( + &self, + token_id: &EsdtTokenIdentifier, + token_nonce: u64, + amount: &BigUint, + ) -> Result<(), ManagedBuffer> { + let result = self + .tx() + .to(ToSelf) + .typed(UserBuiltinProxy) + .esdt_local_burn(token_id, token_nonce, amount) + .returns(ReturnsHandledOrError::new()) + .sync_call_fallible(); + + result + .map_err(|error_code| self.format_error(BURN_ESDT_FAILED, token_id.clone(), error_code)) } - fn is_native_token(&self, token_identifier: &TokenIdentifier) -> bool { - let esdt_safe_native_token_mapper = self.native_token(); - - if esdt_safe_native_token_mapper.is_empty() { - return false; - } - - token_identifier == &esdt_safe_native_token_mapper.get() + #[inline] + fn try_esdt_local_mint( + &self, + token_id: &EsdtTokenIdentifier, + token_nonce: u64, + amount: &BigUint, + ) -> Result<(), ManagedBuffer> { + let result = self + .tx() + .to(ToSelf) + .typed(UserBuiltinProxy) + .esdt_local_mint(token_id, token_nonce, amount) + .returns(ReturnsHandledOrError::new()) + .sync_call_fallible(); + + result + .map_err(|error_code| self.format_error(MINT_ESDT_FAILED, token_id.clone(), error_code)) } #[inline] - fn is_fungible(self, token_type: &EsdtTokenType) -> bool { + fn is_fungible(&self, token_type: &EsdtTokenType) -> bool { *token_type == EsdtTokenType::Fungible } #[inline] - fn is_sft_or_meta(self, token_type: &EsdtTokenType) -> bool { + fn is_sft_or_meta(&self, token_type: &EsdtTokenType) -> bool { *token_type == EsdtTokenType::SemiFungible || *token_type == EsdtTokenType::DynamicSFT - || *token_type == EsdtTokenType::Meta + || *token_type == EsdtTokenType::MetaFungible || *token_type == EsdtTokenType::DynamicMeta } #[inline] - fn is_nft(self, token_type: &EsdtTokenType) -> bool { - *token_type == EsdtTokenType::NonFungible || *token_type == EsdtTokenType::DynamicNFT + fn is_nft(&self, token_type: &EsdtTokenType) -> bool { + *token_type == EsdtTokenType::NonFungible + || *token_type == EsdtTokenType::NonFungibleV2 + || *token_type == EsdtTokenType::DynamicNFT } } diff --git a/common/cross-chain/src/lib.rs b/common/cross-chain/src/lib.rs index 5815f4f6a..8c44ade11 100644 --- a/common/cross-chain/src/lib.rs +++ b/common/cross-chain/src/lib.rs @@ -5,21 +5,36 @@ use structs::configs::EsdtSafeConfig; multiversx_sc::imports!(); pub mod deposit_common; -pub mod events; pub mod execute_common; pub mod storage; pub const MAX_TRANSFERS_PER_TX: usize = 10; pub const DEFAULT_ISSUE_COST: u64 = 50_000_000_000_000_000; // 0.05 EGLD pub const REGISTER_GAS: u64 = 60_000_000; -pub const MAX_GAS_PER_TRANSACTION: u64 = 600_000_000; +pub const MAX_GAS_PER_TRANSACTION: u64 = 500_000_000; #[multiversx_sc::module] pub trait LibCommon: crate::storage::CrossChainStorage { - fn require_esdt_config_valid(&self, config: &EsdtSafeConfig) { - require!( - config.max_tx_gas_limit < MAX_GAS_PER_TRANSACTION, - MAX_GAS_LIMIT_PER_TX_EXCEEDED - ); + fn is_esdt_safe_config_valid(&self, config: &EsdtSafeConfig) -> Option<&str> { + if config.max_tx_gas_limit <= MAX_GAS_PER_TRANSACTION { + None + } else { + Some(MAX_GAS_LIMIT_PER_TX_EXCEEDED) + } + } + + fn resolve_esdt_safe_config( + &self, + opt_config: OptionalValue>, + ) -> EsdtSafeConfig { + match opt_config { + OptionalValue::Some(cfg) => { + if let Some(error_message) = self.is_esdt_safe_config_valid(&cfg) { + sc_panic!(error_message); + } + cfg + } + OptionalValue::None => EsdtSafeConfig::default_config(), + } } } diff --git a/common/cross-chain/src/storage.rs b/common/cross-chain/src/storage.rs index 1a4804267..a72d72fb8 100644 --- a/common/cross-chain/src/storage.rs +++ b/common/cross-chain/src/storage.rs @@ -1,5 +1,4 @@ -use proxies::fee_market_proxy::FeeType; -use structs::{aliases::TxNonce, configs::EsdtSafeConfig, EsdtInfo}; +use structs::{aliases::TxNonce, configs::EsdtSafeConfig, fee::FeeType, EsdtInfo}; multiversx_sc::imports!(); @@ -8,52 +7,52 @@ pub trait CrossChainStorage { #[storage_mapper("lastTxNonce")] fn last_tx_nonce(&self) -> SingleValueMapper; + #[storage_mapper("sovTokenPrefix")] + fn sov_token_prefix(&self) -> SingleValueMapper>; + #[storage_mapper("crossChainConfig")] fn esdt_safe_config(&self) -> SingleValueMapper>; #[storage_mapper("feeMarketAddress")] fn fee_market_address(&self) -> SingleValueMapper; - #[storage_mapper("headerVerifierAddress")] - fn header_verifier_address(&self) -> SingleValueMapper; - - #[storage_mapper("sovToMxTokenId")] + #[view(getSovToMvxTokenId)] + #[storage_mapper("sovToMvxTokenId")] fn sovereign_to_multiversx_token_id_mapper( &self, - sov_token_id: &TokenIdentifier, - ) -> SingleValueMapper; + sov_token_id: &EgldOrEsdtTokenIdentifier, + ) -> SingleValueMapper>; - #[storage_mapper("mxToSovTokenId")] + #[view(getMvxToSovTokenId)] + #[storage_mapper("mvxToSovTokenId")] fn multiversx_to_sovereign_token_id_mapper( &self, - mx_token_id: &TokenIdentifier, - ) -> SingleValueMapper; + mvx_token_id: &EgldOrEsdtTokenIdentifier, + ) -> SingleValueMapper>; + #[view(getSovEsdtTokenInfo)] #[storage_mapper("sovEsdtTokenInfoMapper")] fn sovereign_to_multiversx_esdt_info_mapper( &self, - token_identifier: &TokenIdentifier, + token_identifier: &EgldOrEsdtTokenIdentifier, nonce: u64, ) -> SingleValueMapper>; - #[storage_mapper("mxEsdtTokenInfoMapper")] + #[view(getMvxEsdtTokenInfo)] + #[storage_mapper("mvxEsdtTokenInfoMapper")] fn multiversx_to_sovereign_esdt_info_mapper( &self, - token_identifier: &TokenIdentifier, + token_identifier: &EgldOrEsdtTokenIdentifier, nonce: u64, ) -> SingleValueMapper>; #[view(getNativeToken)] #[storage_mapper("nativeToken")] - fn native_token(&self) -> SingleValueMapper>; + fn native_token(&self) -> SingleValueMapper>; #[storage_mapper("isSovereignChain")] fn is_sovereign_chain(&self) -> SingleValueMapper; - #[view(getMaxBridgedAmount)] - #[storage_mapper("maxBridgedAmount")] - fn max_bridged_amount(&self, token_id: &TokenIdentifier) -> SingleValueMapper; - #[storage_mapper_from_address("feeEnabledFlag")] fn external_fee_enabled( &self, @@ -64,6 +63,6 @@ pub trait CrossChainStorage { fn external_token_fee( &self, sc_address: ManagedAddress, - token_id: &TokenIdentifier, + token_id: &EsdtTokenIdentifier, ) -> SingleValueMapper, ManagedAddress>; } diff --git a/common/custom-events/Cargo.toml b/common/custom-events/Cargo.toml new file mode 100644 index 000000000..07567ecee --- /dev/null +++ b/common/custom-events/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "custom-events" +version = "0.1.0" +authors = ["you"] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.multiversx-sc-modules] +version = "0.63.0" + +[dependencies.structs] +path = "../structs" diff --git a/common/custom-events/src/lib.rs b/common/custom-events/src/lib.rs new file mode 100644 index 000000000..1a68343d3 --- /dev/null +++ b/common/custom-events/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] + +use structs::{ + aliases::{EventPaymentTuple, TxId}, + configs::{EsdtSafeConfig, SovereignConfig}, + fee::{AddressPercentagePair, FeeStruct}, + operation::OperationData, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait CustomEventsModule { + #[event("deposit")] + fn deposit_event( + &self, + #[indexed] dest_address: &ManagedAddress, + #[indexed] tokens: &MultiValueEncoded>, + event_data: OperationData, + ); + + #[event("scCall")] + fn sc_call_event( + &self, + #[indexed] dest_address: &ManagedAddress, + event_data: OperationData, + ); + + #[event("executedBridgeOp")] + fn execute_bridge_operation_event( + &self, + #[indexed] hash_of_hashes: &ManagedBuffer, + #[indexed] hash_of_bridge_op: &ManagedBuffer, + error_message: Option, + ); + + #[event("register")] + fn register_event( + &self, + #[indexed] id: &BigUint, + #[indexed] address: &ManagedAddress, + #[indexed] bls_key: &ManagedBuffer, + #[indexed] egld_stake: &BigUint, + #[indexed] token_stake: &Option>>, + ); + + #[event("unregister")] + fn unregister_event( + &self, + #[indexed] id: &BigUint, + #[indexed] address: &ManagedAddress, + #[indexed] bls_key: &ManagedBuffer, + #[indexed] egld_stake: &BigUint, + #[indexed] token_stake: &Option>>, + ); + + #[event("completeGenesisPhase")] + fn complete_genesis_event(&self); + + #[event("setFee")] + fn set_fee_event(&self, #[indexed] fee_struct: FeeStruct, op_nonce: TxId); + + #[event("removeFee")] + fn remove_fee_event( + &self, + #[indexed] token_id: EgldOrEsdtTokenIdentifier, + op_nonce: TxId, + ); + + #[event("distributeFees")] + fn distribute_fees_event( + &self, + #[indexed] operation: ManagedVec>, + op_nonce: TxId, + ); + + #[event("updateSovereignConfig")] + fn update_sovereign_config_event( + &self, + #[indexed] sovereign_config: SovereignConfig, + op_nonce: TxId, + ); + + #[event("updateRegistrationStatus")] + fn update_registration_status_event(&self, #[indexed] registration_status: u8, op_nonce: TxId); + + #[event("updateEsdtSafeConfig")] + fn update_esdt_safe_config_event( + &self, + #[indexed] esdt_safe_config: EsdtSafeConfig, + op_nonce: TxId, + ); + + #[event("addUsersToFeeWhitelist")] + fn add_users_to_fee_whitelist_event( + &self, + #[indexed] users: ManagedVec>, + op_nonce: TxId, + ); + + #[event("removeUsersFromFeeWhitelist")] + fn remove_users_from_fee_whitelist_event( + &self, + #[indexed] users: ManagedVec>, + op_nonce: TxId, + ); + + #[event("registerToken")] + fn register_token_event( + &self, + #[indexed] token_id: EgldOrEsdtTokenIdentifier, + #[indexed] token_type: EsdtTokenType, + #[indexed] token_name: ManagedBuffer, + #[indexed] token_ticker: ManagedBuffer, + #[indexed] token_decimals: usize, + op_data: OperationData, + ); + + #[event("setTokenBurnMechanism")] + fn set_token_burn_mechanism_event( + &self, + #[indexed] token_id: EgldOrEsdtTokenIdentifier, + op_nonce: TxId, + ); + + #[event("setTokenLockMechanism")] + fn set_token_lock_mechanism_event( + &self, + #[indexed] token_id: EgldOrEsdtTokenIdentifier, + op_nonce: TxId, + ); +} diff --git a/common/error-messages/src/lib.rs b/common/error-messages/src/lib.rs index 4198e4545..397236bbe 100644 --- a/common/error-messages/src/lib.rs +++ b/common/error-messages/src/lib.rs @@ -1,52 +1,142 @@ #![no_std] -pub const BRIDGE_ALREADY_DEPLOYED: &str = "Bridge already deployed"; -pub const INVALID_MIN_MAX_VALIDATOR_NUMBERS: &str = "Invalid min/max validator numbers"; -pub const INVALID_PAYMENT_AMOUNT: &str = "Invalid payment amount"; -pub const ONLY_DEPLOYED_CONTRACTS_CAN_CALL_ENDPOINT: &str = - "Only deployed contracts may call this endpoint"; -pub const NOTHING_TO_TRANSFER: &str = "Nothing to transfer"; -pub const TOO_MANY_TOKENS: &str = "Too many tokens"; -pub const TOKEN_ALREADY_REGISTERED: &str = "This token was already registered"; -pub const TOKEN_BLACKLISTED: &str = "Token is blacklisted"; +pub const ACTION_IS_NOT_ALLOWED: &str = "action is not allowed"; +pub const ADDRESS_NOT_VALID_SC_ADDRESS: &str = "The given address is not a valid SC address"; +pub const AMOUNT_IS_TOO_LARGE: &str = "Amount is too large"; pub const BANNED_ENDPOINT_NAME: &str = "Banned endpoint name"; -pub const GAS_LIMIT_TOO_HIGH: &str = "Gas limit too high"; -pub const NO_HEADER_VERIFIER_ADDRESS: &str = "There is no registered Header-Verifier address"; -pub const MAX_GAS_LIMIT_PER_TX_EXCEEDED: &str = - "The gas limit exceeds the maximum gas per transaction limit"; -pub const DEPOSIT_OVER_MAX_AMOUNT: &str = "Deposit over max amount"; -pub const INVALID_CALLER: &str = "Invalid caller"; -pub const SETUP_PHASE_NOT_COMPLETED: &str = "The setup is not completed"; -pub const ONLY_ESDT_SAFE_CALLER: &str = "Only ESDT Safe can call this endpoint"; -pub const INVALID_FEE: &str = "Invalid fee"; -pub const INVALID_ESDT_IDENTIFIER: &str = "Invalid ESDT identifier"; -pub const INVALID_WEGLD_USDC_PAIR_ADDRESS: &str = "Invalid WEGLD-USDC pair address from router"; -pub const INVALID_TOKEN_USDC_PAIR_ADDRESS: &str = "Invalid TOKEN-USDC pair address from router"; -pub const INVALID_PERCENTAGE_SUM: &str = "Invalid percentage sum"; -pub const INVALID_TOKEN_PROVIDED_FOR_FEE: &str = "Invalid token provided for fee"; -pub const PAYMENT_DOES_NOT_COVER_FEE: &str = "Payment does not cover fee"; -pub const OUTGOING_TX_HASH_ALREADY_REGISTERED: &str = - "The OutGoingTxHash has already been registered"; -pub const BLS_SIGNATURE_NOT_VALID: &str = "BLS signature is not valid"; -pub const CURRENT_OPERATION_NOT_REGISTERED: &str = "The current operation is not registered"; +pub const BURN_MECHANISM_NON_ESDT_TOKENS: &str = "Non-ESDT tokens can not have a burn mechanism"; +pub const CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN: &str = + "The current caller has not deployed any Sovereign Chain"; +pub const CALLER_NOT_FROM_CURRENT_SOVEREIGN: &str = + "Caller is not from the current Sovereign-Chain"; +pub const CALLER_NOT_OWNER: &str = "Endpoint can only be called by owner"; +pub const CALLER_IS_BLACKLISTED: &str = "Caller is blacklisted"; +pub const CANNOT_REGISTER_TOKEN: &str = "Cannot register token"; +pub const CHAIN_CONFIG_ALREADY_DEPLOYED: &str = "The Chain-Config contract is already deployed"; +pub const CHAIN_CONFIG_NOT_DEPLOYED: &str = "The Chain-Config SC is not deployed"; +pub const CHAIN_ID_ALREADY_IN_USE: &str = "This chain ID is already used"; +pub const INVALID_CHAIN_ID: &str = "Invalid chain ID"; +pub const CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC: &str = "Chain ID is not lowercase alphanumeric"; pub const CURRENT_OPERATION_ALREADY_IN_EXECUTION: &str = "The current operation is already in execution"; -pub const NO_ESDT_SAFE_ADDRESS: &str = "There is no registered ESDT address"; +pub const CURRENT_OPERATION_NOT_REGISTERED: &str = "The current operation is not registered"; +pub const DEPLOY_COST_NOT_ENOUGH: &str = + "The given deploy cost is not equal to the standard amount"; +pub const DEPOSIT_OVER_MAX_AMOUNT: &str = "Deposit over max amount"; +pub const ERR_EMPTY_PAYMENTS: &str = "No payments"; +pub const ESDT_SAFE_ADDRESS_NOT_SET: &str = "The ESDT-Safe address is not set"; +pub const ESDT_SAFE_ALREADY_DEPLOYED: &str = "The ESDT-Safe SC is already deployed"; +pub const ESDT_SAFE_NOT_DEPLOYED: &str = + "The ESDT-Safe SC is not deployed, you skipped the second phase"; +pub const ESDT_SAFE_STILL_PAUSED: &str = "Cannot create transaction while paused"; +pub const EXPECTED_MAPPED_TOKEN: &str = "Expected mapped token, got None"; +pub const FAILED_TO_PARSE_AS_NUMBER: &str = "Failed to parse actual amount as number"; +pub const FAILED_TO_REGISTER_SOVEREIGN_TOKEN: &str = "Failed to register sovereign token"; +pub const FEE_MARKET_ALREADY_DEPLOYED: &str = "The Fee-Market SC is already deployed"; +pub const FEE_MARKET_NOT_DEPLOYED: &str = "The Fee-Market SC is not deployed"; +pub const FEE_MARKET_NOT_SET: &str = "There is no Fee-Market address set"; +pub const GAS_LIMIT_TOO_HIGH: &str = "Gas limit too high"; pub const HASH_OF_HASHES_DOES_NOT_MATCH: &str = "Hash of all operations doesn't match the hash of transfer data"; -pub const ESDT_SAFE_STILL_PAUSED: &str = "Cannot create transaction while paused"; -pub const INVALID_TYPE: &str = "Invalid type"; +pub const HEADER_VERIFIER_ALREADY_DEPLOYED: &str = + "The Header-Verifier contract is already deployed"; +pub const HEADER_VERIFIER_NOT_DEPLOYED: &str = + "The Header-Verifier SC is not deployed, you skipped the fourth phase"; +pub const INVALID_CALLER: &str = "Invalid caller"; +pub const INVALID_FEE: &str = "Invalid fee"; pub const INVALID_FEE_TYPE: &str = "Invalid fee type"; -pub const INVALID_TOKEN_ID: &str = "Invalid token ID"; +pub const INVALID_MIN_MAX_VALIDATOR_NUMBERS: &str = "Invalid validator range"; +pub const INVALID_PERCENTAGE_SUM: &str = "Invalid percentage sum"; pub const INVALID_SC_ADDRESS: &str = "Invalid SC address"; +pub const INVALID_TOKEN_ID: &str = "Invalid token ID"; +pub const INVALID_TOKEN_PROVIDED_FOR_FEE: &str = "Invalid token provided for fee"; +pub const INVALID_TYPE: &str = "Invalid type"; pub const ITEM_NOT_IN_LIST: &str = "Item not found in list"; -pub const TOKEN_ID_NO_PREFIX: &str = "Token Id does not have prefix"; -pub const TOKEN_NOT_ACCEPTED_AS_FEE: &str = "Token not accepted as fee"; -pub const CANNOT_REGISTER_TOKEN: &str = "Cannot register token"; -pub const TOKEN_ID_IS_NOT_TRUSTED: &str = "Token is not trusted"; +pub const LOCK_MECHANISM_NON_ESDT: &str = "Non-ESDT tokens can not have a lock mechanism"; +pub const MAX_GAS_LIMIT_PER_TX_EXCEEDED: &str = + "The gas limit exceeds the maximum gas per transaction limit"; pub const MINT_AND_BURN_ROLES_NOT_FOUND: &str = "This token does not have Mint and Burn roles"; -pub const TOKEN_IS_FROM_SOVEREIGN: &str = "Token is from a Sovereign Chain, it cannot be locked"; -pub const DEPOSIT_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT: &str = - "The deposit amount should not be less than the payment amount"; pub const NATIVE_TOKEN_ALREADY_REGISTERED: &str = "Native token was already registered"; -pub const ERR_EMPTY_PAYMENTS: &str = "No payments"; +pub const NATIVE_TOKEN_NOT_REGISTERED: &str = "There is no native token registered"; +pub const NO_ADDRESSES_AVAILABLE: &str = "No addresses available"; +pub const NO_ESDT_SAFE_ADDRESS: &str = "There is no registered ESDT address"; +pub const NO_KNOWN_CHAIN_CONFIG_SC: &str = "No known Chain Config SC contract, deploy first"; +pub const NO_KNOWN_CHAIN_FACTORY_IN_THE_SPECIFIED_SHARD: &str = + "No chain factory address found for the specified shard"; +pub const NO_KNOWN_CHAIN_FACTORY_SC: &str = "No known Chain Factory SC, deploy first"; +pub const NO_KNOWN_DYNAMIC_META_ESDT_TOKEN_ID: &str = "No known Dynamic Meta ESDT token ID"; +pub const NO_KNOWN_DYNAMIC_NFT_TOKEN_ID: &str = "No known Dynamic NFT token ID"; +pub const NO_KNOWN_DYNAMIC_SFT_TOKEN_ID: &str = "No known Dynamic SFT token ID"; +pub const NO_KNOWN_FIRST_TOKEN: &str = "No known first token, register first"; +pub const NO_KNOWN_FEE_MARKET: &str = "No known Fee Market contract, deploy first"; +pub const NO_KNOWN_FEE_TOKEN: &str = "No known fee token, register first"; +pub const NO_KNOWN_TRUSTED_TOKEN: &str = "No known trusted token, register first"; +pub const NO_KNOWN_FUNGIBLE_TOKEN: &str = "No known fungible token, register first"; +pub const NO_KNOWN_MVX_ESDT_SAFE: &str = "No known MVX ESDT Safe contract, deploy first"; +pub const NO_KNOWN_HEADER_VERIFIER: &str = "No known Header Verifier contract, deploy first"; +pub const NO_KNOWN_SOVEREIGN_FORGE_SC: &str = "No known Sovereign Forge SC, deploy first"; +pub const NO_KNOWN_META_ESDT_TOKEN: &str = "No known Meta ESDT token ID"; +pub const NO_KNOWN_NFT_TOKEN: &str = "No known NFT token, register first"; +pub const NO_KNOWN_SFT_TOKEN: &str = "No known SFT token, register first"; +pub const NO_KNOWN_TESTING_SC: &str = "No known Testing SC contract, deploy first"; +pub const NOTHING_TO_TRANSFER: &str = "Nothing to transfer"; +pub const ONLY_ESDT_SAFE_CALLER: &str = "Only ESDT Safe can call this endpoint"; +pub const OUTGOING_TX_HASH_ALREADY_REGISTERED: &str = + "The OutGoingTxHash has already been registered"; +pub const PAYMENT_DOES_NOT_COVER_FEE: &str = "Payment does not cover fee"; +pub const SETUP_PHASE_ALREADY_COMPLETED: &str = "The setup is completed"; +pub const SETUP_PHASE_NOT_COMPLETED: &str = "The setup is not completed"; +pub const SOVEREIGN_SETUP_PHASE_ALREADY_COMPLETED: &str = + "This Sovereign-Chain's setup phase is already completed"; +pub const TOKEN_ALREADY_REGISTERED: &str = "This token was already registered"; +pub const TOKEN_BLACKLISTED: &str = "Token is blacklisted"; +pub const TOKEN_ID_IS_NOT_TRUSTED: &str = "Token is not trusted"; +pub const TOKEN_ID_NO_PREFIX: &str = "Token Id does not have prefix"; +pub const INVALID_PREFIX: &str = "The sovereign prefix should be between 1 and 4 characters long"; +pub const INVALID_PREFIX_FOR_REGISTER: &str = + "Provided sovereign token identifier has invalid prefix"; +pub const TOKEN_NOT_ACCEPTED_AS_FEE: &str = "Token not accepted as fee"; +pub const TOO_MANY_TOKENS: &str = "Too many tokens"; +pub const ERROR_AT_GENERATING_OPERATION_HASH: &str = "Error at generating operation hash"; +pub const NOT_ENOUGH_EGLD_FOR_REGISTER: &str = "Not enough EGLD for registering a new token"; +pub const VALIDATOR_RANGE_EXCEEDED: &str = "Validator range exceeded"; +pub const NOT_ENOUGH_VALIDATORS: &str = "Not enough validators registered"; +pub const VALIDATOR_NOT_REGISTERED: &str = "Validator not registered"; +pub const VALIDATOR_ALREADY_REGISTERED: &str = "Validator already registered"; +pub const BLS_KEY_NOT_REGISTERED: &str = "BLS key not registered"; +pub const VALIDATORS_ALREADY_REGISTERED_IN_EPOCH: &str = + "There already is a validator set registered for this epoch"; +pub const INVALID_ADDITIONAL_STAKE: &str = "Invalid additional stake sent"; +pub const INVALID_EGLD_STAKE: &str = "Invalid EGLD stake sent"; +pub const ADDITIONAL_STAKE_ZERO_VALUE: &str = "Additional stake cannot be a zero value"; +pub const ADDITIONAL_STAKE_NOT_REQUIRED: &str = "Additional stake was provided but is not required"; +pub const DUPLICATE_ADDITIONAL_STAKE_TOKEN_ID: &str = "Duplicate additional stake token identifier"; +pub const INVALID_BLS_KEY_FOR_CALLER: &str = "Invalid BLS key for caller"; +pub const INVALID_EPOCH: &str = "Cannot change the validator set for the genesis epoch"; +pub const CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE: &str = + "The Chain-Config SC setup phase is not completed"; +pub const DEPOSIT_AMOUNT_NOT_ENOUGH: &str = "Deposit amount is less than the operation amount"; +pub const CHAIN_FACTORY_ADDRESS_NOT_IN_EXPECTED_SHARD: &str = + "This Chain-Factory SC is not deployed in the specified shard ID"; +pub const INVALID_BLS_KEY_PROVIDED: &str = "Invalid BLS key has been provided"; +pub const REGISTRATIONS_DISABLED_GENESIS_PHASE: &str = + "Registrations are disabled after genesis phase"; +pub const VALIDATOR_ID_NOT_REGISTERED: &str = "Provided validator id is not registered"; +pub const INVALID_VALIDATOR_DATA: &str = "Invalid validator data has been provided"; +pub const ISSUE_COST_NOT_COVERED: &str = "Native token issue cost is not covered"; +pub const EGLD_TOKEN_IDENTIFIER_EXPECTED: &str = + "The token identifier should be the EGLD token identifier"; +pub const INCORRECT_OPERATION_NONCE: &str = "The operation nonce is incorrect"; +pub const INVALID_FUNCTION_NOT_FOUND: &str = "invalid function (not found)"; +pub const MINT_ESDT_FAILED: &str = "Failed to mint ESDT:"; +pub const CREATE_ESDT_FAILED: &str = "Failed to create ESDT:"; +pub const BURN_ESDT_FAILED: &str = "Failed to burn ESDT:"; +pub const INCORRECT_DEPOSIT_AMOUNT: &str = "Incorrect deposit amount"; +pub const NO_VALIDATORS_FOR_GIVEN_EPOCH: &str = + "There are no registered validators for the given epoch"; +pub const NO_VALIDATORS_FOR_PREVIOUS_EPOCH: &str = + "There are no registered validators for the previous epoch"; +pub const TOKEN_ALREADY_REGISTERED_WITH_BURN_MECHANISM: &str = + "Token already registered in burn mechanism"; +pub const TOKEN_NOT_REGISTERED_WITH_BURN_MECHANISM: &str = "Token not registered in burn mechanism"; +pub const TOKEN_NOT_REGISTERED: &str = "Token not registered"; diff --git a/common/fee-common/Cargo.toml b/common/fee-common/Cargo.toml new file mode 100644 index 000000000..482804a32 --- /dev/null +++ b/common/fee-common/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "fee-common" +version = "0.1.0" +authors = ["you"] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.multiversx-sc-modules] +version = "0.63.0" + +[dependencies.structs] +path = "../structs" + +[dependencies.proxies] +path = "../proxies" + +[dependencies.common-utils] +path = "../common-utils" + +[dependencies.error-messages] +path = "../error-messages" + +[dependencies.custom-events] +path = "../custom-events" diff --git a/common/fee-common/src/endpoints.rs b/common/fee-common/src/endpoints.rs new file mode 100644 index 000000000..97a8a92fb --- /dev/null +++ b/common/fee-common/src/endpoints.rs @@ -0,0 +1,52 @@ +use structs::{aliases::GasLimit, fee::FinalPayment}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeCommonEndpointsModule: + crate::helpers::FeeCommonHelpersModule + + crate::storage::FeeCommonStorageModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule +{ + #[payable("*")] + #[endpoint(subtractFee)] + fn subtract_fee( + &self, + original_caller: ManagedAddress, + total_transfers: usize, + opt_gas_limit: OptionalValue, + ) -> FinalPayment { + self.require_caller_esdt_safe(); + + let caller = self.blockchain().get_caller(); + let payment = self.call_value().single_esdt().clone(); + + if !self.is_fee_enabled() || self.users_whitelist().contains(&original_caller) { + self.tx().to(&caller).payment(payment.clone()).transfer(); + + return FinalPayment { + fee: EsdtTokenPayment::new(payment.token_identifier.clone(), 0, BigUint::zero()), + remaining_tokens: payment, + }; + } + + let final_payment = self.subtract_fee_by_type(payment, total_transfers, opt_gas_limit); + + self.tokens_for_fees() + .insert(final_payment.fee.token_identifier.clone()); + + self.accumulated_fees(&final_payment.fee.token_identifier) + .update(|amt| *amt += &final_payment.fee.amount); + + if final_payment.remaining_tokens.amount > 0 { + self.tx() + .to(&original_caller) + .payment(&final_payment.remaining_tokens) + .transfer(); + } + + final_payment + } +} diff --git a/common/fee-common/src/helpers.rs b/common/fee-common/src/helpers.rs new file mode 100644 index 000000000..86d2e610e --- /dev/null +++ b/common/fee-common/src/helpers.rs @@ -0,0 +1,212 @@ +use error_messages::{ + INVALID_FEE, INVALID_FEE_TYPE, INVALID_PERCENTAGE_SUM, INVALID_TOKEN_ID, + INVALID_TOKEN_PROVIDED_FOR_FEE, PAYMENT_DOES_NOT_COVER_FEE, TOKEN_NOT_ACCEPTED_AS_FEE, +}; +use structs::{ + aliases::GasLimit, + fee::{AddressPercentagePair, FeeStruct, FeeType, FinalPayment, SubtractPaymentArguments}, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub const TOTAL_PERCENTAGE: usize = 10_000; + +#[multiversx_sc::module] +pub trait FeeCommonHelpersModule: + crate::storage::FeeCommonStorageModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule +{ + fn distribute_token_fees( + &self, + pairs: &ManagedVec>, + ) { + let percentage_total = BigUint::from(TOTAL_PERCENTAGE); + + for token_id in self.tokens_for_fees().iter() { + let accumulated_fees = self.accumulated_fees(&token_id).get(); + if accumulated_fees == 0u32 { + continue; + } + + let mut remaining_fees = accumulated_fees.clone(); + + for pair in pairs { + let amount_to_send = self.calculate_fee_amount( + &accumulated_fees, + pair.percentage, + &percentage_total, + ); + + if amount_to_send > 0 { + remaining_fees -= &amount_to_send; + self.send_fee_payment(&pair.address, &token_id, amount_to_send); + } + } + + self.accumulated_fees(&token_id).set(&remaining_fees); + } + } + + fn distribute_fees_and_reset( + &self, + pairs: &ManagedVec>, + ) { + self.distribute_token_fees(pairs); + self.tokens_for_fees().clear(); + } + + fn remove_fee_from_storage(&self, token_id: &EgldOrEsdtTokenIdentifier) { + self.token_fee(token_id).clear(); + self.fee_enabled().set(false); + } + + fn parse_pairs( + &self, + address_percentage_pairs: MultiValueEncoded>, + ) -> ManagedVec> { + let mut pairs = ManagedVec::>::new(); + + for pair in address_percentage_pairs { + let (address, percentage) = pair.into_tuple(); + let pair_struct = AddressPercentagePair { + address, + percentage, + }; + + pairs.push(pair_struct); + } + + pairs + } + + fn calculate_fee_amount( + &self, + total_fees: &BigUint, + percentage: usize, + percentage_total: &BigUint, + ) -> BigUint { + (total_fees * &BigUint::from(percentage)) / percentage_total + } + + fn send_fee_payment( + &self, + address: &ManagedAddress, + token_id: &EsdtTokenIdentifier, + amount: BigUint, + ) { + self.tx() + .to(address) + .payment(EsdtTokenPayment::new(token_id.clone(), 0, amount)) + .transfer(); + } + + fn validate_percentage_sum( + &self, + pairs: &ManagedVec>, + ) -> Option { + let percentage_sum: u64 = pairs.iter().map(|pair| pair.percentage as u64).sum(); + + if percentage_sum != TOTAL_PERCENTAGE as u64 { + return Some(ManagedBuffer::from(INVALID_PERCENTAGE_SUM)); + } + + None + } + + fn subtract_fee_by_type( + &self, + payment: EsdtTokenPayment, + total_transfers: usize, + opt_gas_limit: OptionalValue, + ) -> FinalPayment { + let fee_type = self + .token_fee(&EgldOrEsdtTokenIdentifier::esdt( + payment.token_identifier.clone(), + )) + .get(); + match fee_type { + FeeType::None => sc_panic!(TOKEN_NOT_ACCEPTED_AS_FEE), + FeeType::Fixed { + token, + per_transfer, + per_gas, + } => { + let args = SubtractPaymentArguments { + fee_token: token, + per_transfer, + per_gas, + payment, + total_transfers, + opt_gas_limit, + }; + self.subtract_fee_same_token(args) + } + } + } + + fn subtract_fee_same_token( + &self, + args: SubtractPaymentArguments, + ) -> FinalPayment { + require!( + args.payment.token_identifier == args.fee_token, + INVALID_TOKEN_PROVIDED_FOR_FEE + ); + + let mut total_fee = args.per_transfer * args.total_transfers as u32; + if let OptionalValue::Some(gas_limit) = args.opt_gas_limit { + total_fee += args.per_gas * gas_limit; + } + + let mut payment = args.payment; + require!(total_fee <= payment.amount, PAYMENT_DOES_NOT_COVER_FEE); + + payment.amount -= &total_fee; + + FinalPayment { + fee: EsdtTokenPayment::new(payment.token_identifier.clone(), 0, total_fee), + remaining_tokens: payment, + } + } + + fn set_fee_in_storage(&self, fee_struct: &FeeStruct) -> Option<&str> { + if !self.is_valid_token_id(&fee_struct.base_token) { + return Some(INVALID_TOKEN_ID); + } + + match &fee_struct.fee_type { + FeeType::None => return Some(INVALID_FEE_TYPE), + FeeType::Fixed { token, .. } => { + if &fee_struct.base_token != token { + return Some(INVALID_FEE); + } + } + }; + + self.fee_enabled().set(true); + self.token_fee(&fee_struct.base_token) + .set(fee_struct.fee_type.clone()); + + None + } + + fn init_fee_market( + &self, + esdt_safe_address: ManagedAddress, + fee: Option>, + ) { + self.require_sc_address(&esdt_safe_address); + self.esdt_safe_address().set(esdt_safe_address); + + match fee { + Some(fee_struct) => { + if let Some(err_msg) = self.set_fee_in_storage(&fee_struct) { + sc_panic!(err_msg); + } + } + None => self.fee_enabled().set(false), + } + } +} diff --git a/common/fee-common/src/lib.rs b/common/fee-common/src/lib.rs new file mode 100644 index 000000000..a0e446720 --- /dev/null +++ b/common/fee-common/src/lib.rs @@ -0,0 +1,18 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub mod endpoints; +pub mod helpers; +pub mod storage; + +#[multiversx_sc::module] +pub trait FeeCommonModule: + crate::helpers::FeeCommonHelpersModule + + crate::storage::FeeCommonStorageModule + + crate::endpoints::FeeCommonEndpointsModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule +{ +} diff --git a/common/fee-common/src/storage.rs b/common/fee-common/src/storage.rs new file mode 100644 index 000000000..c96e1b9ff --- /dev/null +++ b/common/fee-common/src/storage.rs @@ -0,0 +1,41 @@ +use error_messages::ONLY_ESDT_SAFE_CALLER; +use structs::fee::FeeType; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeCommonStorageModule { + fn require_caller_esdt_safe(&self) { + let caller = self.blockchain().get_caller(); + let esdt_safe_address = self.esdt_safe_address().get(); + require!(caller == esdt_safe_address, ONLY_ESDT_SAFE_CALLER); + } + + fn is_fee_enabled(&self) -> bool { + self.fee_enabled().get() + } + + #[view(getTokenFee)] + #[storage_mapper("tokenFee")] + fn token_fee( + &self, + token_id: &EgldOrEsdtTokenIdentifier, + ) -> SingleValueMapper>; + + #[storage_mapper("feeEnabledFlag")] + fn fee_enabled(&self) -> SingleValueMapper; + + #[view(getUsersWhitelist)] + #[storage_mapper("usersWhitelist")] + fn users_whitelist(&self) -> UnorderedSetMapper; + + #[storage_mapper("accFees")] + fn accumulated_fees(&self, token_id: &EsdtTokenIdentifier) -> SingleValueMapper; + + #[storage_mapper("tokensForFees")] + fn tokens_for_fees(&self) -> UnorderedSetMapper; + + #[storage_mapper("esdtSafeAddress")] + fn esdt_safe_address(&self) -> SingleValueMapper; +} diff --git a/common/proxies/Cargo.toml b/common/proxies/Cargo.toml index 57ea32cee..238260f81 100644 --- a/common/proxies/Cargo.toml +++ b/common/proxies/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dependencies.structs] path = "../structs" diff --git a/common/proxies/src/chain_config_proxy.rs b/common/proxies/src/chain_config_proxy.rs index 291e16d34..facd9ddd8 100644 --- a/common/proxies/src/chain_config_proxy.rs +++ b/common/proxies/src/chain_config_proxy.rs @@ -44,27 +44,15 @@ where Gas: TxGas, { pub fn init< - Arg0: ProxyArg, - Arg1: ProxyArg, - Arg2: ProxyArg>, - Arg3: ProxyArg>, - Arg4: ProxyArg, BigUint>>>, + Arg0: ProxyArg>>, >( self, - min_validators: Arg0, - max_validators: Arg1, - min_stake: Arg2, - admin: Arg3, - additional_stake_required: Arg4, + opt_config: Arg0, ) -> TxTypedDeploy { self.wrapped_tx .payment(NotPayable) .raw_deploy() - .argument(&min_validators) - .argument(&max_validators) - .argument(&min_stake) - .argument(&admin) - .argument(&additional_stake_required) + .argument(&opt_config) .original_result() } } @@ -97,129 +85,142 @@ where To: TxTo, Gas: TxGas, { - pub fn deploy_bridge< - Arg0: ProxyArg>, - Arg1: ProxyArg, - Arg2: ProxyArg>>, - >( + pub fn complete_setup_phase( self, - code: Arg0, - min_valid_signers: Arg1, - signers: Arg2, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("deployBridge") - .argument(&code) - .argument(&min_valid_signers) - .argument(&signers) + .raw_call("completeSetupPhase") .original_result() } - pub fn min_validators( + pub fn register< + Arg0: ProxyArg>, + >( self, - ) -> TxTypedCall { + bls_key: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("register") + .argument(&bls_key) + .original_result() + } + + pub fn register_bls_key< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + validator_operation: Arg1, + ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("getMinValidators") + .raw_call("registerBlsKey") + .argument(&hash_of_hashes) + .argument(&validator_operation) .original_result() } - pub fn max_validators( + pub fn unregister< + Arg0: ProxyArg>, + >( self, - ) -> TxTypedCall { + bls_key: Arg0, + ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("getMaxValidators") + .raw_call("unregister") + .argument(&bls_key) .original_result() } - pub fn min_stake( + pub fn unregister_bls_key< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( self, - ) -> TxTypedCall> { + hash_of_hashes: Arg0, + validator_operation: Arg1, + ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("getMinStake") + .raw_call("unregisterBlsKey") + .argument(&hash_of_hashes) + .argument(&validator_operation) .original_result() } - pub fn additional_stake_required( + pub fn sovereign_config( self, - ) -> TxTypedCall>> { + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("getAdditionalStakeRequired") + .raw_call("sovereignConfig") .original_result() } - pub fn was_previously_slashed< - Arg0: ProxyArg>, + pub fn bls_key_to_id_mapper< + Arg0: ProxyArg>, >( self, - validator: Arg0, - ) -> TxTypedCall { + bls_key: Arg0, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("wasPreviouslySlashed") - .argument(&validator) + .raw_call("blsKeyToId") + .argument(&bls_key) .original_result() } - pub fn is_admin< - Arg0: ProxyArg>, + pub fn validator_info< + Arg0: ProxyArg>, >( self, - address: Arg0, - ) -> TxTypedCall { + id: Arg0, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("isAdmin") - .argument(&address) + .raw_call("validator_info") + .argument(&id) .original_result() } - pub fn add_admin< - Arg0: ProxyArg>, - >( + pub fn bls_keys_map( self, - address: Arg0, - ) -> TxTypedCall { + ) -> TxTypedCall, ManagedBuffer>>> { self.wrapped_tx .payment(NotPayable) - .raw_call("addAdmin") - .argument(&address) + .raw_call("blsKeysMap") .original_result() } - pub fn remove_admin< - Arg0: ProxyArg>, + pub fn update_sovereign_config_during_setup_phase< + Arg0: ProxyArg>, >( self, - address: Arg0, + new_config: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("removeAdmin") - .argument(&address) + .raw_call("updateSovereignConfigSetupPhase") + .argument(&new_config) .original_result() } - pub fn admins( + pub fn update_sovereign_config< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( self, - ) -> TxTypedCall>> { + hash_of_hashes: Arg0, + update_config_operation: Arg1, + ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("getAdmins") + .raw_call("updateSovereignConfig") + .argument(&hash_of_hashes) + .argument(&update_config_operation) .original_result() } } - -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem)] -pub struct TokenIdAmountPair -where - Api: ManagedTypeApi, -{ - pub token_id: TokenIdentifier, - pub amount: BigUint, -} diff --git a/common/proxies/src/chain_factory_proxy.rs b/common/proxies/src/chain_factory_proxy.rs index 482aedaf5..bffa87739 100644 --- a/common/proxies/src/chain_factory_proxy.rs +++ b/common/proxies/src/chain_factory_proxy.rs @@ -8,7 +8,6 @@ #![allow(clippy::all)] use multiversx_sc::proxy_imports::*; -use structs::configs::{EsdtSafeConfig, SovereignConfig}; pub struct ChainFactoryContractProxy; @@ -55,7 +54,7 @@ where sovereign_forge_address: Arg0, chain_config_template: Arg1, header_verifier_template: Arg2, - cross_chain_operation_template: Arg3, + mvx_esdt_safe_template: Arg3, fee_market_template: Arg4, ) -> TxTypedDeploy { self.wrapped_tx @@ -64,7 +63,7 @@ where .argument(&sovereign_forge_address) .argument(&chain_config_template) .argument(&header_verifier_template) - .argument(&cross_chain_operation_template) + .argument(&mvx_esdt_safe_template) .argument(&fee_market_template) .original_result() } @@ -99,110 +98,99 @@ where Gas: TxGas, { pub fn deploy_sovereign_chain_config_contract< - Arg0: ProxyArg>, + Arg0: ProxyArg>>, >( self, - config: Arg0, + opt_config: Arg0, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) .raw_call("deploySovereignChainConfigContract") - .argument(&config) + .argument(&opt_config) .original_result() } pub fn deploy_header_verifier< - Arg0: ProxyArg>, + Arg0: ProxyArg>>, >( self, - chain_config_address: Arg0, + sovereign_contracts: Arg0, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) .raw_call("deployHeaderVerifier") - .argument(&chain_config_address) + .argument(&sovereign_contracts) .original_result() } - pub fn set_esdt_safe_address_in_header_verifier< + pub fn deploy_mvx_esdt_safe< Arg0: ProxyArg>, - Arg1: ProxyArg>, + Arg1: ProxyArg>, + Arg2: ProxyArg>>, >( self, - header_verifier: Arg0, - esdt_safe_address: Arg1, - ) -> TxTypedCall { + sovereign_owner: Arg0, + sov_token_prefix: Arg1, + opt_config: Arg2, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("setEsdtSafeAddressInHeaderVerifier") - .argument(&header_verifier) - .argument(&esdt_safe_address) + .raw_call("deployEsdtSafe") + .argument(&sovereign_owner) + .argument(&sov_token_prefix) + .argument(&opt_config) .original_result() } - pub fn deploy_enshrine_esdt_safe< - Arg0: ProxyArg, - Arg1: ProxyArg>, - Arg2: ProxyArg>, - Arg3: ProxyArg>, - Arg4: ProxyArg>>, + pub fn deploy_fee_market< + Arg0: ProxyArg>, + Arg1: ProxyArg>>, >( self, - is_sovereign_chain: Arg0, - token_handler_address: Arg1, - wegld_identifier: Arg2, - sov_token_prefix: Arg3, - opt_config: Arg4, + esdt_safe_address: Arg0, + fee: Arg1, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("deployEnshrineEsdtSafe") - .argument(&is_sovereign_chain) - .argument(&token_handler_address) - .argument(&wegld_identifier) - .argument(&sov_token_prefix) - .argument(&opt_config) + .raw_call("deployFeeMarket") + .argument(&esdt_safe_address) + .argument(&fee) .original_result() } - pub fn deploy_esdt_safe< - Arg0: ProxyArg, - Arg1: ProxyArg>, - >( + pub fn chain_config_template( self, - is_sovereign_chain: Arg0, - header_verifier_address: Arg1, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("deployEsdtSafe") - .argument(&is_sovereign_chain) - .argument(&header_verifier_address) + .raw_call("getChainConfigTemplateAddress") .original_result() } - pub fn deploy_fee_market< - Arg0: ProxyArg>, - Arg1: ProxyArg>>, - >( + pub fn header_verifier_template( self, - esdt_safe_address: Arg0, - fee: Arg1, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("deployFeeMarket") - .argument(&esdt_safe_address) - .argument(&fee) + .raw_call("getHeaderVerifierTemplateAddress") .original_result() } - pub fn complete_setup_phase( + pub fn esdt_safe_template( self, - ) -> TxTypedCall { + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("completeSetupPhase") + .raw_call("getEsdtSafeTemplateAddress") + .original_result() + } + + pub fn fee_market_template( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getFeeMarketTemplateAddress") .original_result() } @@ -253,4 +241,154 @@ where .raw_call("getAdmins") .original_result() } + + pub fn update_esdt_safe_config< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + esdt_safe_address: Arg0, + new_config: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateEsdtSafeConfig") + .argument(&esdt_safe_address) + .argument(&new_config) + .original_result() + } + + pub fn update_sovereign_config< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + chain_config_address: Arg0, + new_config: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateSovereignConfig") + .argument(&chain_config_address) + .argument(&new_config) + .original_result() + } + + pub fn set_fee< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + fee_market_address: Arg0, + new_fee: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setFee") + .argument(&fee_market_address) + .argument(&new_fee) + .original_result() + } + + pub fn remove_fee< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + fee_market_address: Arg0, + token_id: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeFee") + .argument(&fee_market_address) + .argument(&token_id) + .original_result() + } + + pub fn add_users_to_whitelist< + Arg0: ProxyArg>, + Arg1: ProxyArg>>, + >( + self, + fee_market_address: Arg0, + users: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addUsersToWhitelistSetupPhase") + .argument(&fee_market_address) + .argument(&users) + .original_result() + } + + pub fn remove_users_from_whitelist< + Arg0: ProxyArg>, + Arg1: ProxyArg>>, + >( + self, + fee_market_address: Arg0, + users: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeUsersFromWhitelistSetupPhase") + .argument(&fee_market_address) + .argument(&users) + .original_result() + } + + pub fn set_token_burn_mechanism< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + mvx_esdt_safe_address: Arg0, + token_id: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setTokenBurnMechanismSetupPhase") + .argument(&mvx_esdt_safe_address) + .argument(&token_id) + .original_result() + } + + pub fn set_token_lock_mechanism< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + mvx_esdt_safe_address: Arg0, + token_id: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setTokenLockMechanismSetupPhase") + .argument(&mvx_esdt_safe_address) + .argument(&token_id) + .original_result() + } + + pub fn complete_setup_phase< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + Arg2: ProxyArg>, + Arg3: ProxyArg>, + >( + self, + chain_config_address: Arg0, + header_verifier_address: Arg1, + mvx_esdt_safe_address: Arg2, + fee_market_address: Arg3, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("completeSetupPhase") + .argument(&chain_config_address) + .argument(&header_verifier_address) + .argument(&mvx_esdt_safe_address) + .argument(&fee_market_address) + .original_result() + } } diff --git a/common/proxies/src/header_verifier_proxy.rs b/common/proxies/src/header_verifier_proxy.rs index d0ae2e460..45a798802 100644 --- a/common/proxies/src/header_verifier_proxy.rs +++ b/common/proxies/src/header_verifier_proxy.rs @@ -43,12 +43,16 @@ where From: TxFrom, Gas: TxGas, { - pub fn init( + pub fn init< + Arg0: ProxyArg>>, + >( self, + sovereign_contracts: Arg0, ) -> TxTypedDeploy { self.wrapped_tx .payment(NotPayable) .raw_deploy() + .argument(&sovereign_contracts) .original_result() } } @@ -81,16 +85,28 @@ where To: TxTo, Gas: TxGas, { - pub fn register_bls_pub_keys< - Arg0: ProxyArg>>, - >( + pub fn complete_setup_phase( self, - bls_pub_keys: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("registerBlsPubKeys") - .argument(&bls_pub_keys) + .raw_call("completeSetupPhase") + .original_result() + } + + pub fn operation_hash_status< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + operation_hash: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("operationHashStatus") + .argument(&hash_of_hashes) + .argument(&operation_hash) .original_result() } @@ -98,23 +114,23 @@ where Arg0: ProxyArg>, Arg1: ProxyArg>, Arg2: ProxyArg>, - Arg3: ProxyArg>, + Arg3: ProxyArg, Arg4: ProxyArg>>, >( self, signature: Arg0, - bridge_operations_hash: Arg1, - _pub_keys_bitmap: Arg2, - _epoch: Arg3, + hash_of_hashes: Arg1, + pub_keys_bitmap: Arg2, + epoch: Arg3, operations_hashes: Arg4, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) .raw_call("registerBridgeOps") .argument(&signature) - .argument(&bridge_operations_hash) - .argument(&_pub_keys_bitmap) - .argument(&_epoch) + .argument(&hash_of_hashes) + .argument(&pub_keys_bitmap) + .argument(&epoch) .argument(&operations_hashes) .original_result() } @@ -124,39 +140,26 @@ where Arg1: ProxyArg>, Arg2: ProxyArg>, Arg3: ProxyArg>, - Arg4: ProxyArg>, - Arg5: ProxyArg>>, + Arg4: ProxyArg, + Arg5: ProxyArg>>, >( self, signature: Arg0, - bridge_operations_hash: Arg1, + hash_of_hashes: Arg1, operation_hash: Arg2, - _pub_keys_bitmap: Arg3, - _epoch: Arg4, - _pub_keys_id: Arg5, + pub_keys_bitmap: Arg3, + epoch: Arg4, + pub_keys_id: Arg5, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) .raw_call("changeValidatorSet") .argument(&signature) - .argument(&bridge_operations_hash) + .argument(&hash_of_hashes) .argument(&operation_hash) - .argument(&_pub_keys_bitmap) - .argument(&_epoch) - .argument(&_pub_keys_id) - .original_result() - } - - pub fn set_esdt_safe_address< - Arg0: ProxyArg>, - >( - self, - esdt_safe_address: Arg0, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setEsdtSafeAddress") - .argument(&esdt_safe_address) + .argument(&pub_keys_bitmap) + .argument(&epoch) + .argument(&pub_keys_id) .original_result() } @@ -167,7 +170,7 @@ where self, hash_of_hashes: Arg0, operation_hash: Arg1, - ) -> TxTypedCall { + ) -> TxTypedCall>> { self.wrapped_tx .payment(NotPayable) .raw_call("removeExecutedHash") @@ -179,16 +182,19 @@ where pub fn lock_operation_hash< Arg0: ProxyArg>, Arg1: ProxyArg>, + Arg2: ProxyArg, >( self, hash_of_hashes: Arg0, operation_hash: Arg1, - ) -> TxTypedCall { + operation_nonce: Arg2, + ) -> TxTypedCall>> { self.wrapped_tx .payment(NotPayable) .raw_call("lockOperationHash") .argument(&hash_of_hashes) .argument(&operation_hash) + .argument(&operation_nonce) .original_result() } } diff --git a/common/proxies/src/lib.rs b/common/proxies/src/lib.rs index 3691bea0d..3228aa9cd 100644 --- a/common/proxies/src/lib.rs +++ b/common/proxies/src/lib.rs @@ -1,8 +1,11 @@ #![no_std] pub mod chain_config_proxy; pub mod chain_factory_proxy; -pub mod fee_market_proxy; pub mod header_verifier_proxy; pub mod mvx_esdt_safe_proxy; +pub mod mvx_fee_market_proxy; pub mod sov_esdt_safe_proxy; +pub mod sov_fee_market_proxy; +pub mod sov_registrar_proxy; +pub mod sovereign_forge_proxy; pub mod testing_sc_proxy; diff --git a/common/proxies/src/mvx_esdt_safe_proxy.rs b/common/proxies/src/mvx_esdt_safe_proxy.rs index fcfbdeb43..dc5f79009 100644 --- a/common/proxies/src/mvx_esdt_safe_proxy.rs +++ b/common/proxies/src/mvx_esdt_safe_proxy.rs @@ -45,16 +45,22 @@ where { pub fn init< Arg0: ProxyArg>, - Arg1: ProxyArg>>, + Arg1: ProxyArg>, + Arg2: ProxyArg>, + Arg3: ProxyArg>>, >( self, - header_verifier_address: Arg0, - opt_config: Arg1, + sovereign_owner: Arg0, + sovereign_forge_address: Arg1, + sov_token_prefix: Arg2, + opt_config: Arg3, ) -> TxTypedDeploy { self.wrapped_tx .payment(NotPayable) .raw_deploy() - .argument(&header_verifier_address) + .argument(&sovereign_owner) + .argument(&sovereign_forge_address) + .argument(&sov_token_prefix) .argument(&opt_config) .original_result() } @@ -88,7 +94,7 @@ where To: TxTo, Gas: TxGas, { - pub fn update_configuration< + pub fn update_esdt_safe_config_during_setup_phase< Arg0: ProxyArg>, >( self, @@ -96,11 +102,43 @@ where ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("updateConfiguration") + .raw_call("updateEsdtSafeConfigSetupPhase") .argument(&new_config) .original_result() } + pub fn update_esdt_safe_config< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + update_config_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateEsdtSafeConfig") + .argument(&hash_of_hashes) + .argument(&update_config_operation) + .original_result() + } + + pub fn switch_pause_status< + Arg0: ProxyArg>, + Arg1: ProxyArg, + >( + self, + hash_of_hashes: Arg0, + pause_status_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("pauseContract") + .argument(&hash_of_hashes) + .argument(&pause_status_operation) + .original_result() + } + pub fn set_fee_market_address< Arg0: ProxyArg>, >( @@ -114,19 +152,12 @@ where .original_result() } - pub fn set_max_bridged_amount< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( + pub fn complete_setup_phase( self, - token_id: Arg0, - max_amount: Arg1, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("setMaxBridgedAmount") - .argument(&token_id) - .argument(&max_amount) + .raw_call("completeSetupPhase") .original_result() } @@ -161,27 +192,19 @@ where .original_result() } - pub fn register_token< - Arg0: ProxyArg>, - Arg1: ProxyArg, - Arg2: ProxyArg>, - Arg3: ProxyArg>, - Arg4: ProxyArg, + pub fn register_sovereign_token< + Arg0: ProxyArg>, + Arg1: ProxyArg>, >( self, - sov_token_id: Arg0, - token_type: Arg1, - token_display_name: Arg2, - token_ticker: Arg3, - num_decimals: Arg4, - ) -> TxTypedCall { + hash_of_hashes: Arg0, + register_token_operation: Arg1, + ) -> TxTypedCall { self.wrapped_tx + .payment(NotPayable) .raw_call("registerToken") - .argument(&sov_token_id) - .argument(&token_type) - .argument(&token_display_name) - .argument(&token_ticker) - .argument(&num_decimals) + .argument(&hash_of_hashes) + .argument(®ister_token_operation) .original_result() } @@ -190,61 +213,151 @@ where Arg1: ProxyArg>, >( self, - token_ticker: Arg0, - token_name: Arg1, + ticker: Arg0, + name: Arg1, ) -> TxTypedCall { self.wrapped_tx .raw_call("registerNativeToken") - .argument(&token_ticker) - .argument(&token_name) + .argument(&ticker) + .argument(&name) .original_result() } - pub fn set_token_burn_mechanism< - Arg0: ProxyArg>, + pub fn set_token_burn_mechanism_setup_phase< + Arg0: ProxyArg>, >( self, token_id: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("setTokenBurnMechanism") + .raw_call("setTokenBurnMechanismSetupPhase") .argument(&token_id) .original_result() } - pub fn set_token_lock_mechanism< - Arg0: ProxyArg>, + pub fn set_token_burn_mechanism< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + set_burn_mechanism_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setTokenBurnMechanism") + .argument(&hash_of_hashes) + .argument(&set_burn_mechanism_operation) + .original_result() + } + + pub fn set_token_lock_mechanism_setup_phase< + Arg0: ProxyArg>, >( self, token_id: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("setTokenLockMechanism") + .raw_call("setTokenLockMechanismSetupPhase") .argument(&token_id) .original_result() } - pub fn native_token( + pub fn set_token_lock_mechanism< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( self, - ) -> TxTypedCall> { + hash_of_hashes: Arg0, + set_lock_mechanism_operation: Arg1, + ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("getNativeToken") + .raw_call("setTokenLockMechanism") + .argument(&hash_of_hashes) + .argument(&set_lock_mechanism_operation) .original_result() } - pub fn max_bridged_amount< - Arg0: ProxyArg>, + pub fn deposited_tokens_amount< + Arg0: ProxyArg>, >( self, - token_id: Arg0, + token_identifier: Arg0, ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("getMaxBridgedAmount") - .argument(&token_id) + .raw_call("getDepositedTokensAmount") + .argument(&token_identifier) + .original_result() + } + + pub fn sovereign_to_multiversx_token_id_mapper< + Arg0: ProxyArg>, + >( + self, + sov_token_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getSovToMvxTokenId") + .argument(&sov_token_id) + .original_result() + } + + pub fn multiversx_to_sovereign_token_id_mapper< + Arg0: ProxyArg>, + >( + self, + mvx_token_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getMvxToSovTokenId") + .argument(&mvx_token_id) + .original_result() + } + + pub fn sovereign_to_multiversx_esdt_info_mapper< + Arg0: ProxyArg>, + Arg1: ProxyArg, + >( + self, + token_identifier: Arg0, + nonce: Arg1, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getSovEsdtTokenInfo") + .argument(&token_identifier) + .argument(&nonce) + .original_result() + } + + pub fn multiversx_to_sovereign_esdt_info_mapper< + Arg0: ProxyArg>, + Arg1: ProxyArg, + >( + self, + token_identifier: Arg0, + nonce: Arg1, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getMvxEsdtTokenInfo") + .argument(&token_identifier) + .argument(&nonce) + .original_result() + } + + pub fn native_token( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getNativeToken") .original_result() } diff --git a/common/proxies/src/mvx_fee_market_proxy.rs b/common/proxies/src/mvx_fee_market_proxy.rs new file mode 100644 index 000000000..6bbe27382 --- /dev/null +++ b/common/proxies/src/mvx_fee_market_proxy.rs @@ -0,0 +1,271 @@ +// 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 MvxFeeMarketProxy; + +impl TxProxyTrait for MvxFeeMarketProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = MvxFeeMarketProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + MvxFeeMarketProxyMethods { wrapped_tx: tx } + } +} + +pub struct MvxFeeMarketProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl MvxFeeMarketProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init< + Arg0: ProxyArg>, + Arg1: ProxyArg>>, + >( + self, + esdt_safe_address: Arg0, + fee: Arg1, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .argument(&esdt_safe_address) + .argument(&fee) + .original_result() + } +} + +#[rustfmt::skip] +impl MvxFeeMarketProxyMethods +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 MvxFeeMarketProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn complete_setup_phase( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("completeSetupPhase") + .original_result() + } + + pub fn distribute_fees< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("distributeFees") + .argument(&hash_of_hashes) + .argument(&operation) + .original_result() + } + + pub fn remove_fee_during_setup_phase< + Arg0: ProxyArg>, + >( + self, + base_token: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeFeeDuringSetupPhase") + .argument(&base_token) + .original_result() + } + + pub fn remove_fee< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + remove_fee_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeFee") + .argument(&hash_of_hashes) + .argument(&remove_fee_operation) + .original_result() + } + + pub fn set_fee_during_setup_phase< + Arg0: ProxyArg>, + >( + self, + fee_struct: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setFeeDuringSetupPhase") + .argument(&fee_struct) + .original_result() + } + + pub fn set_fee< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + set_fee_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setFee") + .argument(&hash_of_hashes) + .argument(&set_fee_operation) + .original_result() + } + + pub fn token_fee< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getTokenFee") + .argument(&token_id) + .original_result() + } + + pub fn users_whitelist( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getUsersWhitelist") + .original_result() + } + + pub fn subtract_fee< + Arg0: ProxyArg>, + Arg1: ProxyArg, + Arg2: ProxyArg>, + >( + self, + original_caller: Arg0, + total_transfers: Arg1, + opt_gas_limit: Arg2, + ) -> TxTypedCall> { + self.wrapped_tx + .raw_call("subtractFee") + .argument(&original_caller) + .argument(&total_transfers) + .argument(&opt_gas_limit) + .original_result() + } + + pub fn add_users_to_whitelist_during_setup_phase< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addUsersToWhitelistSetupPhase") + .argument(&users) + .original_result() + } + + pub fn add_users_to_whitelist< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + add_to_whitelist_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addUsersToWhitelist") + .argument(&hash_of_hashes) + .argument(&add_to_whitelist_operation) + .original_result() + } + + pub fn remove_users_from_whitelist_during_setup_phase< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeUsersFromWhitelistSetupPhase") + .argument(&users) + .original_result() + } + + pub fn remove_users_from_whitelist< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + >( + self, + hash_of_hashes: Arg0, + remove_from_whitelist_operation: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeUsersFromWhitelist") + .argument(&hash_of_hashes) + .argument(&remove_from_whitelist_operation) + .original_result() + } +} diff --git a/common/proxies/src/sov_esdt_safe_proxy.rs b/common/proxies/src/sov_esdt_safe_proxy.rs index 52337597a..402791e0b 100644 --- a/common/proxies/src/sov_esdt_safe_proxy.rs +++ b/common/proxies/src/sov_esdt_safe_proxy.rs @@ -88,6 +88,30 @@ where To: TxTo, Gas: TxGas, { + pub fn register_token< + Arg0: ProxyArg>, + Arg1: ProxyArg, + Arg2: ProxyArg>, + Arg3: ProxyArg>, + Arg4: ProxyArg, + >( + self, + token_id: Arg0, + token_type: Arg1, + token_name: Arg2, + token_ticker: Arg3, + token_decimals: Arg4, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("registerToken") + .argument(&token_id) + .argument(&token_type) + .argument(&token_name) + .argument(&token_ticker) + .argument(&token_decimals) + .original_result() + } + pub fn update_configuration< Arg0: ProxyArg>, >( @@ -114,22 +138,6 @@ where .original_result() } - pub fn set_max_bridged_amount< - Arg0: ProxyArg>, - Arg1: ProxyArg>, - >( - self, - token_id: Arg0, - max_amount: Arg1, - ) -> TxTypedCall { - self.wrapped_tx - .payment(NotPayable) - .raw_call("setMaxBridgedAmount") - .argument(&token_id) - .argument(&max_amount) - .original_result() - } - pub fn deposit< Arg0: ProxyArg>, Arg1: ProxyArg, MultiValueEncoded>>>>, @@ -145,25 +153,70 @@ where .original_result() } - pub fn native_token( + pub fn sovereign_to_multiversx_token_id_mapper< + Arg0: ProxyArg>, + >( self, - ) -> TxTypedCall> { + sov_token_id: Arg0, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("getNativeToken") + .raw_call("getSovToMvxTokenId") + .argument(&sov_token_id) .original_result() } - pub fn max_bridged_amount< - Arg0: ProxyArg>, + pub fn multiversx_to_sovereign_token_id_mapper< + Arg0: ProxyArg>, >( self, - token_id: Arg0, - ) -> TxTypedCall> { + mvx_token_id: Arg0, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("getMaxBridgedAmount") - .argument(&token_id) + .raw_call("getMvxToSovTokenId") + .argument(&mvx_token_id) + .original_result() + } + + pub fn sovereign_to_multiversx_esdt_info_mapper< + Arg0: ProxyArg>, + Arg1: ProxyArg, + >( + self, + token_identifier: Arg0, + nonce: Arg1, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getSovEsdtTokenInfo") + .argument(&token_identifier) + .argument(&nonce) + .original_result() + } + + pub fn multiversx_to_sovereign_esdt_info_mapper< + Arg0: ProxyArg>, + Arg1: ProxyArg, + >( + self, + token_identifier: Arg0, + nonce: Arg1, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getMvxEsdtTokenInfo") + .argument(&token_identifier) + .argument(&nonce) + .original_result() + } + + pub fn native_token( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getNativeToken") .original_result() } diff --git a/common/proxies/src/fee_market_proxy.rs b/common/proxies/src/sov_fee_market_proxy.rs similarity index 66% rename from common/proxies/src/fee_market_proxy.rs rename to common/proxies/src/sov_fee_market_proxy.rs index 58527eb45..3cc518ce4 100644 --- a/common/proxies/src/fee_market_proxy.rs +++ b/common/proxies/src/sov_fee_market_proxy.rs @@ -9,23 +9,23 @@ use multiversx_sc::proxy_imports::*; -pub struct FeeMarketProxy; +pub struct SovFeeMarketProxy; -impl TxProxyTrait for FeeMarketProxy +impl TxProxyTrait for SovFeeMarketProxy where Env: TxEnv, From: TxFrom, To: TxTo, Gas: TxGas, { - type TxProxyMethods = FeeMarketProxyMethods; + type TxProxyMethods = SovFeeMarketProxyMethods; fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { - FeeMarketProxyMethods { wrapped_tx: tx } + SovFeeMarketProxyMethods { wrapped_tx: tx } } } -pub struct FeeMarketProxyMethods +pub struct SovFeeMarketProxyMethods where Env: TxEnv, From: TxFrom, @@ -36,7 +36,7 @@ where } #[rustfmt::skip] -impl FeeMarketProxyMethods +impl SovFeeMarketProxyMethods where Env: TxEnv, Env::Api: VMApi, @@ -45,7 +45,7 @@ where { pub fn init< Arg0: ProxyArg>, - Arg1: ProxyArg>>, + Arg1: ProxyArg>>, >( self, esdt_safe_address: Arg0, @@ -61,7 +61,7 @@ where } #[rustfmt::skip] -impl FeeMarketProxyMethods +impl SovFeeMarketProxyMethods where Env: TxEnv, Env::Api: VMApi, @@ -80,7 +80,7 @@ where } #[rustfmt::skip] -impl FeeMarketProxyMethods +impl SovFeeMarketProxyMethods where Env: TxEnv, Env::Api: VMApi, @@ -88,95 +88,90 @@ where To: TxTo, Gas: TxGas, { - pub fn set_price_aggregator_address< - Arg0: ProxyArg>, + pub fn add_users_to_whitelist< + Arg0: ProxyArg>>, >( self, - price_aggregator_address: Arg0, + users: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("setPriceAggregatorAddress") - .argument(&price_aggregator_address) + .raw_call("addUsersToWhitelist") + .argument(&users) .original_result() } - pub fn set_fee< - Arg0: ProxyArg>, + pub fn remove_users_from_whitelist< + Arg0: ProxyArg>>, >( self, - fee_struct: Arg0, + users: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("setFee") - .argument(&fee_struct) + .raw_call("removeUsersFromWhitelist") + .argument(&users) .original_result() } - pub fn remove_fee< - Arg0: ProxyArg>, + pub fn token_fee< + Arg0: ProxyArg>, >( self, - base_token: Arg0, - ) -> TxTypedCall { + token_id: Arg0, + ) -> TxTypedCall> { self.wrapped_tx .payment(NotPayable) - .raw_call("removeFee") - .argument(&base_token) + .raw_call("getTokenFee") + .argument(&token_id) .original_result() } - pub fn token_fee< - Arg0: ProxyArg>, - >( + pub fn users_whitelist( self, - token_id: Arg0, - ) -> TxTypedCall> { + ) -> TxTypedCall>> { self.wrapped_tx .payment(NotPayable) - .raw_call("getTokenFee") - .argument(&token_id) + .raw_call("getUsersWhitelist") .original_result() } - pub fn add_users_to_whitelist< - Arg0: ProxyArg>>, + pub fn distribute_fees< + Arg0: ProxyArg, usize>>>, >( self, - users: Arg0, + address_percentage_pairs: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("addUsersToWhitelist") - .argument(&users) + .raw_call("distributeFees") + .argument(&address_percentage_pairs) .original_result() } - pub fn remove_users_from_whitelist< - Arg0: ProxyArg>>, + pub fn remove_fee< + Arg0: ProxyArg>, >( self, - users: Arg0, + token_id: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("removeUsersFromWhitelist") - .argument(&users) + .raw_call("removeFee") + .argument(&token_id) .original_result() } - /// Percentages have to be between 0 and 10_000, and must all add up to 100% (i.e. 10_000) - pub fn distribute_fees< - Arg0: ProxyArg, usize>>>, + pub fn set_fee< + Arg0: ProxyArg>, >( self, - address_percentage_pairs: Arg0, + fee_struct: Arg0, ) -> TxTypedCall { self.wrapped_tx .payment(NotPayable) - .raw_call("distributeFees") - .argument(&address_percentage_pairs) + .raw_call("setFee") + .argument(&fee_struct) .original_result() } @@ -189,7 +184,7 @@ where original_caller: Arg0, total_transfers: Arg1, opt_gas_limit: Arg2, - ) -> TxTypedCall> { + ) -> TxTypedCall> { self.wrapped_tx .raw_call("subtractFee") .argument(&original_caller) @@ -197,53 +192,4 @@ where .argument(&opt_gas_limit) .original_result() } - - pub fn users_whitelist( - self, - ) -> TxTypedCall>> { - self.wrapped_tx - .payment(NotPayable) - .raw_call("getUsersWhitelist") - .original_result() - } -} - -#[type_abi] -#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] -pub struct FeeStruct -where - Api: ManagedTypeApi, -{ - pub base_token: TokenIdentifier, - pub fee_type: FeeType, -} - -#[rustfmt::skip] -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone)] -pub enum FeeType -where - Api: ManagedTypeApi, -{ - None, - Fixed { - token: TokenIdentifier, - per_transfer: BigUint, - per_gas: BigUint, - }, - AnyToken { - base_fee_token: TokenIdentifier, - per_transfer: BigUint, - per_gas: BigUint, - }, -} - -#[type_abi] -#[derive(TopEncode, TopDecode)] -pub struct FinalPayment -where - Api: ManagedTypeApi, -{ - pub fee: EsdtTokenPayment, - pub remaining_tokens: EsdtTokenPayment, } diff --git a/common/proxies/src/sov_registrar_proxy.rs b/common/proxies/src/sov_registrar_proxy.rs new file mode 100644 index 000000000..d73d549dc --- /dev/null +++ b/common/proxies/src/sov_registrar_proxy.rs @@ -0,0 +1,196 @@ +// 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 SovRegistrarProxy; + +impl TxProxyTrait for SovRegistrarProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = SovRegistrarProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + SovRegistrarProxyMethods { wrapped_tx: tx } + } +} + +pub struct SovRegistrarProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl SovRegistrarProxyMethods +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 SovRegistrarProxyMethods +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 SovRegistrarProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn set_fee< + Arg0: ProxyArg>, + >( + self, + fee_struct: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setFee") + .argument(&fee_struct) + .original_result() + } + + pub fn remove_fee< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeFee") + .argument(&token_id) + .original_result() + } + + pub fn distribute_fees< + Arg0: ProxyArg>>, + >( + self, + address_percentage_pairs: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("distributeFees") + .argument(&address_percentage_pairs) + .original_result() + } + + pub fn add_users_to_fee_whitelist< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addUsersToFeeWhitelist") + .argument(&users) + .original_result() + } + + pub fn remove_users_from_fee_whitelist< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeUsersFromFeeWhitelist") + .argument(&users) + .original_result() + } + + pub fn update_sovereign_config< + Arg0: ProxyArg>, + >( + self, + sovereign_config: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateSovereignConfig") + .argument(&sovereign_config) + .original_result() + } + + pub fn update_esdt_safe_config< + Arg0: ProxyArg>, + >( + self, + esdt_safe_config: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateEsdtSafeConfig") + .argument(&esdt_safe_config) + .original_result() + } + + pub fn token_fee< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getTokenFee") + .argument(&token_id) + .original_result() + } + + pub fn users_whitelist( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getUsersWhitelist") + .original_result() + } +} diff --git a/common/proxies/src/sovereign_forge_proxy.rs b/common/proxies/src/sovereign_forge_proxy.rs new file mode 100644 index 000000000..811892fd7 --- /dev/null +++ b/common/proxies/src/sovereign_forge_proxy.rs @@ -0,0 +1,385 @@ +// 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 SovereignForgeProxy; + +impl TxProxyTrait for SovereignForgeProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = SovereignForgeProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + SovereignForgeProxyMethods { wrapped_tx: tx } + } +} + +pub struct SovereignForgeProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl SovereignForgeProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init< + Arg0: ProxyArg>>, + >( + self, + opt_deploy_cost: Arg0, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .argument(&opt_deploy_cost) + .original_result() + } +} + +#[rustfmt::skip] +impl SovereignForgeProxyMethods +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 SovereignForgeProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn register_chain_factory< + Arg0: ProxyArg, + Arg1: ProxyArg>, + >( + self, + shard_id: Arg0, + chain_factory_address: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("registerChainFactory") + .argument(&shard_id) + .argument(&chain_factory_address) + .original_result() + } + + pub fn register_trusted_token< + Arg0: ProxyArg>, + >( + self, + trusted_token: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("registerTrustedToken") + .argument(&trusted_token) + .original_result() + } + + pub fn deploy_phase_one< + Arg0: ProxyArg>>, + Arg1: ProxyArg>>, + >( + self, + opt_preferred_chain_id: Arg0, + config: Arg1, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("deployPhaseOne") + .argument(&opt_preferred_chain_id) + .argument(&config) + .original_result() + } + + pub fn deploy_phase_two< + Arg0: ProxyArg>>, + >( + self, + opt_config: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("deployPhaseTwo") + .argument(&opt_config) + .original_result() + } + + pub fn deploy_phase_three< + Arg0: ProxyArg>>, + >( + self, + fee: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("deployPhaseThree") + .argument(&fee) + .original_result() + } + + pub fn deploy_phase_four( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("deployPhaseFour") + .original_result() + } + + pub fn complete_setup_phase( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("completeSetupPhase") + .original_result() + } + + pub fn sovereign_deployed_contracts< + Arg0: ProxyArg>, + >( + self, + chain_id: Arg0, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getDeployedSovereignContracts") + .argument(&chain_id) + .original_result() + } + + pub fn trusted_tokens( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getTrustedTokens") + .original_result() + } + + pub fn sovereign_setup_phase< + Arg0: ProxyArg>, + >( + self, + chain_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getSovereignSetupPhase") + .argument(&chain_id) + .original_result() + } + + pub fn chain_factories< + Arg0: ProxyArg, + >( + self, + shard_id: Arg0, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getChainFactoryAddress") + .argument(&shard_id) + .original_result() + } + + pub fn deploy_cost( + self, + ) -> TxTypedCall> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getDeployCost") + .original_result() + } + + pub fn chain_ids( + self, + ) -> TxTypedCall>> { + self.wrapped_tx + .payment(NotPayable) + .raw_call("getAllChainIds") + .original_result() + } + + pub fn update_esdt_safe_config< + Arg0: ProxyArg>, + >( + self, + new_config: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateEsdtSafeConfig") + .argument(&new_config) + .original_result() + } + + pub fn update_sovereign_config< + Arg0: ProxyArg>, + >( + self, + new_config: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateSovereignConfig") + .argument(&new_config) + .original_result() + } + + pub fn set_fee< + Arg0: ProxyArg>, + >( + self, + new_fee: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setFee") + .argument(&new_fee) + .original_result() + } + + pub fn remove_fee< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeFee") + .argument(&token_id) + .original_result() + } + + pub fn add_users_to_whitelist< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("addUsersToWhitelist") + .argument(&users) + .original_result() + } + + pub fn remove_users_from_whitelist< + Arg0: ProxyArg>>, + >( + self, + users: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("removeUsersFromWhitelist") + .argument(&users) + .original_result() + } + + pub fn set_token_burn_mechanism< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setTokenBurnMechanism") + .argument(&token_id) + .original_result() + } + + pub fn set_token_lock_mechanism< + Arg0: ProxyArg>, + >( + self, + token_id: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("setTokenLockMechanism") + .argument(&token_id) + .original_result() + } + + pub fn update_deploy_cost< + Arg0: ProxyArg>, + >( + self, + deploy_cost: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("updateDeployCost") + .argument(&deploy_cost) + .original_result() + } + + pub fn pause_endpoint( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("pause") + .original_result() + } + + pub fn unpause_endpoint( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("unpause") + .original_result() + } + + pub fn paused_status( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("isPaused") + .original_result() + } +} diff --git a/common/proxies/src/testing_sc_proxy.rs b/common/proxies/src/testing_sc_proxy.rs index ecfec3958..f4739dfd1 100644 --- a/common/proxies/src/testing_sc_proxy.rs +++ b/common/proxies/src/testing_sc_proxy.rs @@ -92,4 +92,36 @@ where .argument(&value) .original_result() } + + pub fn read_native_token< + Arg0: ProxyArg>, + >( + self, + wanted_address: Arg0, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("read_native_token") + .argument(&wanted_address) + .original_result() + } + + pub fn send_tokens< + Arg0: ProxyArg>, + Arg1: ProxyArg, + Arg2: ProxyArg>, + >( + self, + token_id: Arg0, + nonce: Arg1, + amount: Arg2, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("send_tokens") + .argument(&token_id) + .argument(&nonce) + .argument(&amount) + .original_result() + } } diff --git a/common/setup-phase/Cargo.toml b/common/setup-phase/Cargo.toml index 11838ff7f..12fbab8c6 100644 --- a/common/setup-phase/Cargo.toml +++ b/common/setup-phase/Cargo.toml @@ -5,10 +5,16 @@ authors = ["dorin-iancu "] edition = "2021" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dependencies.error-messages] path = "../error-messages" +[dependencies.common-utils] +path = "../common-utils" + +[dependencies.custom-events] +path = "../custom-events" + [dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" +version = "0.63.0" diff --git a/common/setup-phase/src/lib.rs b/common/setup-phase/src/lib.rs index 9ee9aa33d..863559a20 100644 --- a/common/setup-phase/src/lib.rs +++ b/common/setup-phase/src/lib.rs @@ -5,7 +5,9 @@ use error_messages::{INVALID_CALLER, SETUP_PHASE_NOT_COMPLETED}; multiversx_sc::imports!(); #[multiversx_sc::module] -pub trait SetupPhaseModule { +pub trait SetupPhaseModule: + common_utils::CommonUtilsModule + custom_events::CustomEventsModule +{ fn require_caller_initiator(&self) { let caller = self.blockchain().get_caller(); let initiator = self.initiator_address().get(); @@ -18,6 +20,20 @@ pub trait SetupPhaseModule { require!(self.is_setup_phase_complete(), SETUP_PHASE_NOT_COMPLETED); } + fn require_setup_complete_with_event( + &self, + hash_of_hashes: &ManagedBuffer, + operation_hash: &ManagedBuffer, + ) { + if !self.is_setup_phase_complete() { + self.complete_operation( + hash_of_hashes, + operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + } + } + #[inline] fn is_setup_phase_complete(&self) -> bool { self.setup_phase_complete().get() diff --git a/common/structs/Cargo.toml b/common/structs/Cargo.toml index 4411f92aa..f15e13682 100644 --- a/common/structs/Cargo.toml +++ b/common/structs/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" path = "src/lib.rs" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" diff --git a/common/structs/src/aliases.rs b/common/structs/src/aliases.rs index bb68dbad5..740b9b257 100644 --- a/common/structs/src/aliases.rs +++ b/common/structs/src/aliases.rs @@ -9,13 +9,13 @@ pub type TxNonce = u64; pub type BlockNonce = u64; pub type SenderAddress = ManagedAddress; pub type ReceiverAddress = ManagedAddress; -pub type EventPaymentTuple = MultiValue3, u64, EsdtTokenData>; -pub type PaymentsVec = ManagedVec>; +pub type EventPaymentTuple = MultiValue3, u64, EsdtTokenData>; +pub type PaymentsVec = ManagedVec>; pub type TransferDataTuple = MultiValue3, MultiValueEncoded>>; pub type ExtractedFeeResult = - MultiValue2>, ManagedVec>>; + MultiValue2>, ManagedVec>>; pub type OptionalValueTransferDataTuple = OptionalValue>; -pub type StakeMultiArg = MultiValue2, BigUint>; +pub type StakeMultiArg = MultiValue2, BigUint>; pub type OptionalTransferData = - OptionalValue, ManagedVec>>>; + OptionalValue, MultiValueEncoded>>>; diff --git a/common/structs/src/configs.rs b/common/structs/src/configs.rs index 165f3ec52..85838c7c2 100644 --- a/common/structs/src/configs.rs +++ b/common/structs/src/configs.rs @@ -1,8 +1,56 @@ -use crate::{aliases::GasLimit, DEFAULT_MAX_TX_GAS_LIMIT}; +use multiversx_sc::api::CryptoApi; + +use crate::{ + aliases::{GasLimit, TxNonce}, + generate_hash::GenerateHash, + DEFAULT_MAX_TX_GAS_LIMIT, +}; multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +#[type_abi] +#[derive( + TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, +)] +pub struct UpdateRegistrationStatusOperation { + pub registration_status: u8, + pub nonce: TxNonce, +} + +#[type_abi] +#[derive( + TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, +)] +pub struct SetBurnMechanismOperation { + pub token_id: EgldOrEsdtTokenIdentifier, + pub nonce: TxNonce, +} + +impl GenerateHash for SetBurnMechanismOperation {} + +#[type_abi] +#[derive( + TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, +)] +pub struct SetLockMechanismOperation { + pub token_id: EgldOrEsdtTokenIdentifier, + pub nonce: TxNonce, +} + +impl GenerateHash for SetLockMechanismOperation {} + +#[type_abi] +#[derive( + TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, +)] +pub struct UpdateSovereignConfigOperation { + pub sovereign_config: SovereignConfig, + pub nonce: TxNonce, +} + +impl GenerateHash for UpdateSovereignConfigOperation {} + #[type_abi] #[derive( TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, @@ -14,6 +62,8 @@ pub struct SovereignConfig { pub opt_additional_stake_required: Option>>, } +impl GenerateHash for SovereignConfig {} + impl SovereignConfig { pub fn new( min_validators: u64, @@ -30,7 +80,11 @@ impl SovereignConfig { } pub fn default_config() -> Self { - SovereignConfig::new(0, 1, BigUint::default(), None) + SovereignConfig::new(2, 50, BigUint::default(), None) + } + + pub fn default_config_for_test() -> Self { + SovereignConfig::new(1, 2, BigUint::default(), None) } } @@ -39,25 +93,58 @@ impl SovereignConfig { TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, )] pub struct StakeArgs { - pub token_id: TokenIdentifier, + pub token_identifier: EsdtTokenIdentifier, pub amount: BigUint, } impl StakeArgs { - pub fn new(token_id: TokenIdentifier, amount: BigUint) -> Self { - StakeArgs { token_id, amount } + pub fn new(token_identifier: EsdtTokenIdentifier, amount: BigUint) -> Self { + StakeArgs { + token_identifier, + amount, + } } } +#[type_abi] +#[derive(TopEncode, TopDecode)] +pub struct PauseStatusOperation { + pub status: bool, + pub nonce: TxNonce, +} +impl GenerateHash for PauseStatusOperation {} + +#[type_abi] +#[derive( + TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone, Debug, PartialEq, +)] +pub struct MaxBridgedAmount { + pub token_id: EgldOrEsdtTokenIdentifier, + pub amount: BigUint, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] +pub struct UpdateEsdtSafeConfigOperation { + pub esdt_safe_config: EsdtSafeConfig, + pub nonce: TxNonce, +} + +impl GenerateHash for UpdateEsdtSafeConfigOperation {} + #[type_abi] #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] pub struct EsdtSafeConfig { - pub token_whitelist: ManagedVec>, - pub token_blacklist: ManagedVec>, + pub token_whitelist: ManagedVec>, + pub token_blacklist: ManagedVec>, pub max_tx_gas_limit: GasLimit, pub banned_endpoints: ManagedVec>, + pub address_blacklist: ManagedVec>, + pub max_bridged_token_amounts: ManagedVec>, } +impl GenerateHash for EsdtSafeConfig {} + impl EsdtSafeConfig { #[inline] pub fn default_config() -> Self { @@ -66,20 +153,26 @@ impl EsdtSafeConfig { token_blacklist: ManagedVec::new(), max_tx_gas_limit: DEFAULT_MAX_TX_GAS_LIMIT, banned_endpoints: ManagedVec::new(), + address_blacklist: ManagedVec::new(), + max_bridged_token_amounts: ManagedVec::new(), } } pub fn new( - token_whitelist: ManagedVec>, - token_blacklist: ManagedVec>, + token_whitelist: ManagedVec>, + token_blacklist: ManagedVec>, max_tx_gas_limit: GasLimit, banned_endpoints: ManagedVec>, + deposit_blacklist: ManagedVec>, + max_bridged_token_amounts: ManagedVec>, ) -> Self { EsdtSafeConfig { token_whitelist, token_blacklist, max_tx_gas_limit, banned_endpoints, + address_blacklist: deposit_blacklist, + max_bridged_token_amounts, } } } diff --git a/common/structs/src/events.rs b/common/structs/src/events.rs index 50eaece6b..1ffe47014 100644 --- a/common/structs/src/events.rs +++ b/common/structs/src/events.rs @@ -6,13 +6,17 @@ multiversx_sc::derive_imports!(); #[type_abi] #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] pub struct EventPayment { - pub identifier: TokenIdentifier, + pub identifier: EgldOrEsdtTokenIdentifier, pub nonce: u64, pub data: EsdtTokenData, } impl EventPayment { - pub fn new(identifier: TokenIdentifier, nonce: u64, data: EsdtTokenData) -> Self { + pub fn new( + identifier: EgldOrEsdtTokenIdentifier, + nonce: u64, + data: EsdtTokenData, + ) -> Self { EventPayment { identifier, nonce, diff --git a/common/structs/src/fee.rs b/common/structs/src/fee.rs new file mode 100644 index 000000000..b1b618e51 --- /dev/null +++ b/common/structs/src/fee.rs @@ -0,0 +1,100 @@ +use multiversx_sc::api::CryptoApi; + +use crate::{ + aliases::{GasLimit, TxNonce}, + generate_hash::GenerateHash, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, PartialEq)] +pub enum FeeType { + None, + Fixed { + token: EgldOrEsdtTokenIdentifier, + per_transfer: BigUint, + per_gas: BigUint, + }, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedDecode, Clone)] +pub struct AddUsersToWhitelistOperation { + pub users: ManagedVec>, + pub nonce: TxNonce, +} + +impl GenerateHash for AddUsersToWhitelistOperation {} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedDecode, Clone)] +pub struct RemoveUsersFromWhitelistOperation { + pub users: ManagedVec>, + pub nonce: TxNonce, +} + +impl GenerateHash for RemoveUsersFromWhitelistOperation {} + +#[type_abi] +#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] +pub struct RemoveFeeOperation { + pub token_id: EgldOrEsdtTokenIdentifier, + pub nonce: TxNonce, +} + +impl GenerateHash for RemoveFeeOperation {} + +#[type_abi] +#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] +pub struct SetFeeOperation { + pub fee_struct: FeeStruct, + pub nonce: TxNonce, +} + +impl GenerateHash for SetFeeOperation {} + +#[type_abi] +#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] +pub struct FeeStruct { + pub base_token: EgldOrEsdtTokenIdentifier, + pub fee_type: FeeType, +} + +impl GenerateHash for FeeStruct {} +impl GenerateHash for EgldOrEsdtTokenIdentifier {} + +#[type_abi] +#[derive(TopEncode, TopDecode)] +pub struct FinalPayment { + pub fee: EsdtTokenPayment, + pub remaining_tokens: EsdtTokenPayment, +} + +#[type_abi] +#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] +pub struct DistributeFeesOperation { + pub pairs: ManagedVec>, + pub nonce: TxNonce, +} + +impl GenerateHash for DistributeFeesOperation {} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone, ManagedVecItem)] +pub struct AddressPercentagePair { + pub address: ManagedAddress, + pub percentage: usize, +} + +impl GenerateHash for AddressPercentagePair {} + +pub struct SubtractPaymentArguments { + pub fee_token: EgldOrEsdtTokenIdentifier, + pub per_transfer: BigUint, + pub per_gas: BigUint, + pub payment: EsdtTokenPayment, + pub total_transfers: usize, + pub opt_gas_limit: OptionalValue, +} diff --git a/common/structs/src/forge.rs b/common/structs/src/forge.rs new file mode 100644 index 000000000..3cb947798 --- /dev/null +++ b/common/structs/src/forge.rs @@ -0,0 +1,27 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem)] +pub struct ContractInfo { + pub id: ScArray, + pub address: ManagedAddress, +} + +impl ContractInfo { + pub fn new(id: ScArray, address: ManagedAddress) -> Self { + ContractInfo { id, address } + } +} + +#[type_abi] +#[derive( + TopEncode, TopDecode, Debug, NestedEncode, NestedDecode, Clone, ManagedVecItem, PartialEq, +)] +pub enum ScArray { + ChainFactory, + HeaderVerifier, + ESDTSafe, + FeeMarket, + ChainConfig, +} diff --git a/common/structs/src/generate_hash.rs b/common/structs/src/generate_hash.rs new file mode 100644 index 000000000..b58dffd8b --- /dev/null +++ b/common/structs/src/generate_hash.rs @@ -0,0 +1,25 @@ +use multiversx_sc::{ + api::{CryptoApi, CryptoApiImpl, SHA256_RESULT_LEN}, + codec::TopEncode, + types::{ManagedBuffer, ManagedByteArray, ManagedType}, +}; + +pub trait GenerateHash +where + Self: TopEncode, +{ + fn generate_hash(&self) -> ManagedBuffer { + let mut serialized_data = ManagedBuffer::::new(); + + if self.top_encode(&mut serialized_data).is_err() { + return ManagedBuffer::new(); + } + + unsafe { + let result: ManagedByteArray = ManagedByteArray::new_uninit(); + A::crypto_api_impl().sha256_managed(result.get_handle(), serialized_data.get_handle()); + + result.as_managed_buffer().clone() + } + } +} diff --git a/common/structs/src/lib.rs b/common/structs/src/lib.rs index 4644be3da..94711283c 100644 --- a/common/structs/src/lib.rs +++ b/common/structs/src/lib.rs @@ -1,28 +1,103 @@ #![no_std] +use crate::{aliases::TxNonce, generate_hash::GenerateHash, operation::OperationData}; +use multiversx_sc::api::CryptoApi; + multiversx_sc::imports!(); multiversx_sc::derive_imports!(); pub mod aliases; pub mod configs; pub mod events; +pub mod fee; +pub mod forge; +pub mod generate_hash; pub mod operation; pub const MIN_BLOCKS_FOR_FINALITY: u64 = 10; -pub const DEFAULT_MAX_TX_GAS_LIMIT: u64 = 300_000_000; +pub const DEFAULT_MAX_TX_GAS_LIMIT: u64 = 500_000_000; + +pub const PHASE_ONE_ASYNC_CALL_GAS: u64 = 9_000_000; +pub const PHASE_ONE_CALLBACK_GAS: u64 = 3_000_000; + +pub const PHASE_TWO_ASYNC_CALL_GAS: u64 = 18_000_000; +pub const PHASE_TWO_CALLBACK_GAS: u64 = 2_000_000; + +pub const PHASE_THREE_ASYNC_CALL_GAS: u64 = 16_000_000; +pub const PHASE_THREE_CALLBACK_GAS: u64 = 2_000_000; + +pub const PHASE_FOUR_ASYNC_CALL_GAS: u64 = 9_000_000; +pub const PHASE_FOUR_CALLBACK_GAS: u64 = 3_000_000; + +pub const COMPLETE_SETUP_PHASE_GAS: u64 = 50_000_000; +pub const COMPLETE_SETUP_PHASE_CALLBACK_GAS: u64 = 3_000_000; + +pub const UPDATE_CONFIGS_GAS: u64 = 8_000_000; +pub const UPDATE_CONFIGS_CALLBACK_GAS: u64 = 1_000_000; + +pub const BLS_KEY_BYTE_LENGTH: usize = 96; #[type_abi] #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] pub struct EsdtInfo { - pub token_identifier: TokenIdentifier, + pub token_identifier: EgldOrEsdtTokenIdentifier, pub token_nonce: u64, } -pub struct IssueEsdtArgs { - pub sov_token_id: TokenIdentifier, +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode)] +pub struct ValidatorInfo { + pub address: ManagedAddress, + pub bls_key: ManagedBuffer, + pub egld_stake: BigUint, + pub token_stake: Option>>, +} + +impl GenerateHash for ValidatorData {} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct ValidatorOperation { + pub validator_data: ValidatorData, + pub nonce: TxNonce, +} + +impl GenerateHash for ValidatorOperation {} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone)] +pub struct ValidatorData { + pub id: BigUint, + pub address: ManagedAddress, + pub bls_key: ManagedBuffer, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode)] +pub struct RegisterTokenOperation { + pub token_id: EgldOrEsdtTokenIdentifier, pub token_type: EsdtTokenType, - pub issue_cost: BigUint, pub token_display_name: ManagedBuffer, pub token_ticker: ManagedBuffer, pub num_decimals: usize, + pub data: OperationData, +} + +#[type_abi] +#[derive(TopEncode, TopDecode, NestedEncode)] +pub struct RegisterTokenStruct { + pub token_id: EgldOrEsdtTokenIdentifier, + pub token_type: EsdtTokenType, + pub token_display_name: ManagedBuffer, + pub token_ticker: ManagedBuffer, + pub num_decimals: usize, +} + +impl GenerateHash for RegisterTokenOperation {} + +#[type_abi] +#[derive(TopEncode, TopDecode, PartialEq, Debug)] +pub enum OperationHashStatus { + NotLocked = 1, + Locked, } diff --git a/common/structs/src/operation.rs b/common/structs/src/operation.rs index 411bcc5a0..dac5364c1 100644 --- a/common/structs/src/operation.rs +++ b/common/structs/src/operation.rs @@ -1,6 +1,11 @@ use aliases::{GasLimit, OptionalValueTransferDataTuple, TxId}; +use multiversx_sc::api::CryptoApi; -use crate::aliases::{self, EventPaymentTuple, TransferDataTuple}; +use crate::{ + aliases::{self, EventPaymentTuple, TransferDataTuple}, + events::EventPayment, + generate_hash::GenerateHash, +}; multiversx_sc::imports!(); multiversx_sc::derive_imports!(); @@ -13,6 +18,8 @@ pub struct Operation { pub data: OperationData, } +impl GenerateHash for Operation {} + impl Operation { #[inline] pub fn new( @@ -27,14 +34,12 @@ impl Operation { let mut tuples = MultiValueEncoded::new(); for token in &self.tokens { - tuples.push( - ( - token.token_identifier.clone(), - token.token_nonce, - token.token_data.clone(), - ) - .into(), - ); + let event_payment: EventPaymentTuple = EventPaymentTuple::from(EventPayment { + identifier: token.token_identifier.clone(), + nonce: token.token_nonce, + data: token.token_data.clone(), + }); + tuples.push(event_payment); } tuples @@ -105,17 +110,10 @@ impl OperationData { } } -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] -pub struct OperationTuple { - pub op_hash: ManagedBuffer, - pub operation: Operation, -} - #[type_abi] #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, ManagedVecItem, Clone)] pub struct OperationEsdtPayment { - pub token_identifier: TokenIdentifier, + pub token_identifier: EgldOrEsdtTokenIdentifier, pub token_nonce: u64, pub token_data: EsdtTokenData, } @@ -123,7 +121,7 @@ pub struct OperationEsdtPayment { impl OperationEsdtPayment { #[inline] pub fn new( - token_identifier: TokenIdentifier, + token_identifier: EgldOrEsdtTokenIdentifier, token_nonce: u64, token_data: EsdtTokenData, ) -> Self { @@ -135,10 +133,10 @@ impl OperationEsdtPayment { } } -impl From> for EsdtTokenPayment { +impl From> for EgldOrEsdtTokenPayment { #[inline] fn from(payment: OperationEsdtPayment) -> Self { - EsdtTokenPayment { + EgldOrEsdtTokenPayment { token_identifier: payment.token_identifier, token_nonce: payment.token_nonce, amount: payment.token_data.amount, @@ -149,16 +147,9 @@ impl From> for EsdtTokenPayment { impl Default for OperationEsdtPayment { fn default() -> Self { OperationEsdtPayment { - token_identifier: TokenIdentifier::from(ManagedBuffer::new()), + token_identifier: EgldOrEsdtTokenIdentifier::from(ManagedBuffer::new()), token_nonce: 0, token_data: EsdtTokenData::default(), } } } - -impl OperationTuple { - #[inline] - pub fn new(op_hash: ManagedBuffer, operation: Operation) -> Self { - OperationTuple { op_hash, operation } - } -} diff --git a/common/token-whitelist/Cargo.toml b/common/token-whitelist/Cargo.toml deleted file mode 100644 index fa2132c0b..000000000 --- a/common/token-whitelist/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "token-whitelist" -version = "0.1.0" -authors = ["dorin-iancu "] -edition = "2021" - -[dependencies.setup-phase] -path = "../setup-phase" - -[dependencies.utils] -path = "../utils" - -[dependencies.error-messages] -path = "../error-messages" - -[dependencies.multiversx-sc] -version = "=0.57.1" - -[dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" diff --git a/common/token-whitelist/src/lib.rs b/common/token-whitelist/src/lib.rs deleted file mode 100644 index 6f6d1d5e7..000000000 --- a/common/token-whitelist/src/lib.rs +++ /dev/null @@ -1,88 +0,0 @@ -#![no_std] - -use error_messages::TOKEN_BLACKLISTED; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait TokenWhitelistModule: setup_phase::SetupPhaseModule + utils::UtilsModule { - /// Tokens in the whitelist can be transferred without fees - #[endpoint(addTokensToWhitelist)] - fn add_tokens_to_whitelist( - &self, - // opt_signature: Option>, - tokens: MultiValueEncoded, - ) { - if !self.is_setup_phase_complete() { - self.require_caller_initiator(); - self.token_whitelist().extend(tokens); - - return; - } - - // let token_list = self.verify_items_signature(opt_signature, tokens); - self.token_whitelist().extend(tokens); - } - - #[endpoint(removeTokensFromWhitelist)] - fn remove_tokens_from_whitelist( - &self, - // opt_signature: Option>, - tokens: MultiValueEncoded, - ) { - if !self.is_setup_phase_complete() { - self.require_caller_initiator(); - self.remove_items(&mut self.token_whitelist(), tokens); - - return; - } - - // let token_list = self.verify_items_signature(opt_signature, tokens); - self.remove_items(&mut self.token_whitelist(), tokens); - } - - fn require_token_not_blacklisted(&self, token_id: &TokenIdentifier) { - require!( - !self.token_blacklist().contains(token_id), - TOKEN_BLACKLISTED - ); - } - - /// Tokens in blacklist cannot be transferred - #[endpoint(addTokensToBlacklist)] - fn add_tokens_to_blacklist( - &self, - // opt_signature: Option>, - tokens: MultiValueEncoded, - ) { - if !self.is_setup_phase_complete() { - self.require_caller_initiator(); - self.token_blacklist().extend(tokens); - - return; - } - - // let token_list = self.verify_items_signature(opt_signature, tokens); - self.token_blacklist().extend(tokens); - } - - #[endpoint(removeTokensFromBlacklist)] - fn remove_tokens_from_blacklist(&self, tokens: MultiValueEncoded) { - if !self.is_setup_phase_complete() { - self.require_caller_initiator(); - self.remove_items(&mut self.token_blacklist(), tokens); - - return; - } - - self.remove_items(&mut self.token_blacklist(), tokens); - } - - #[view(getTokenWhitelist)] - #[storage_mapper("tokenWhitelist")] - fn token_whitelist(&self) -> UnorderedSetMapper; - - #[view(getTokenBlacklist)] - #[storage_mapper("tokenBlacklist")] - fn token_blacklist(&self) -> UnorderedSetMapper; -} diff --git a/common/tx-nonce/Cargo.toml b/common/tx-nonce/Cargo.toml new file mode 100644 index 000000000..8f476bb88 --- /dev/null +++ b/common/tx-nonce/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tx-nonce" +version = "0.1.0" +authors = ["andrei-baltariu "] +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.structs] +path = "../structs" diff --git a/common/tx-nonce/src/lib.rs b/common/tx-nonce/src/lib.rs new file mode 100644 index 000000000..851c81932 --- /dev/null +++ b/common/tx-nonce/src/lib.rs @@ -0,0 +1,19 @@ +#![no_std] + +use structs::aliases::TxNonce; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait TxNonceModule { + #[storage_mapper("lastTxNonce")] + fn last_tx_nonce(&self) -> SingleValueMapper; + + #[inline] + fn get_and_save_next_tx_id(&self) -> TxNonce { + self.last_tx_nonce().update(|last_tx_nonce| { + *last_tx_nonce += 1; + *last_tx_nonce + }) + } +} diff --git a/common/utils/src/lib.rs b/common/utils/src/lib.rs deleted file mode 100644 index b36b30bae..000000000 --- a/common/utils/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![no_std] - -use error_messages::{ - ERR_EMPTY_PAYMENTS, INVALID_SC_ADDRESS, INVALID_TOKEN_ID, ITEM_NOT_IN_LIST, TOKEN_ID_NO_PREFIX, -}; -use structs::aliases::PaymentsVec; - -multiversx_sc::imports!(); - -const DASH: u8 = b'-'; -const MAX_TOKEN_ID_LEN: usize = 32; - -#[multiversx_sc::module] -pub trait UtilsModule { - fn require_sc_address(&self, address: &ManagedAddress) { - require!( - !address.is_zero() && self.blockchain().is_smart_contract(address), - INVALID_SC_ADDRESS - ); - } - - fn require_valid_token_id(&self, token_id: &TokenIdentifier) { - require!(token_id.is_valid_esdt_identifier(), INVALID_TOKEN_ID); - } - - fn remove_items< - T: TopEncode + TopDecode + NestedEncode + NestedDecode + 'static, - I: IntoIterator, - >( - &self, - mapper: &mut UnorderedSetMapper, - items: I, - ) { - for item in items { - let was_removed = mapper.swap_remove(&item); - require!(was_removed, ITEM_NOT_IN_LIST); - } - } - - fn pop_first_payment( - &self, - payments: PaymentsVec, - ) -> MultiValue2>, PaymentsVec> { - require!(!payments.is_empty(), ERR_EMPTY_PAYMENTS); - - let mut new_payments = payments; - - let first_payment = new_payments.get(0).clone(); - new_payments.remove(0); - - MultiValue2::from((OptionalValue::Some(first_payment.clone()), new_payments)) - } - - fn has_prefix(&self, token_id: &TokenIdentifier) -> bool { - let buffer = token_id.as_managed_buffer(); - let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; - let slice = buffer.load_to_byte_array(&mut array_buffer); - - let counter = slice.iter().filter(|&&c| c == DASH).count(); - - if counter == 2 { - return true; - } - - false - } - - #[inline] - fn require_token_has_prefix(&self, token_id: &TokenIdentifier) { - require!(self.has_prefix(token_id), TOKEN_ID_NO_PREFIX); - } - - fn has_sov_prefix(&self, token_id: &TokenIdentifier, chain_prefix: &ManagedBuffer) -> bool { - if !self.has_prefix(token_id) { - return false; - } - - let buffer = token_id.as_managed_buffer(); - let mut array_buffer = [0u8; MAX_TOKEN_ID_LEN]; - let slice = buffer.load_to_byte_array(&mut array_buffer); - - if let Some(index) = slice.iter().position(|&b| b == DASH) { - let prefix = ManagedBuffer::from(&slice[..index]); - - if prefix == chain_prefix.clone() { - return true; - } - } - - false - } -} diff --git a/fee-market/meta/src/main.rs b/fee-market/meta/src/main.rs deleted file mode 100644 index 18d797bbf..000000000 --- a/fee-market/meta/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - multiversx_sc_meta_lib::cli_main::(); -} diff --git a/fee-market/sc-config.toml b/fee-market/sc-config.toml deleted file mode 100644 index e2ed86f9c..000000000 --- a/fee-market/sc-config.toml +++ /dev/null @@ -1,16 +0,0 @@ -[contracts.main] -name = "fee-market" - -[contracts.full] -name = "fee-marker-full" -add-unlabelled = true -add-labels = ["fee-market-external-view"] - -[contracts.view] -name = "fee-market-view" -external-view = true -add-unlabelled = false -add-labels = ["fee-market-external-view"] - -[[proxy]] -path = "../common/proxies/src/fee_market_proxy.rs" diff --git a/fee-market/src/enable_fee.rs b/fee-market/src/enable_fee.rs deleted file mode 100644 index 899f2d9c5..000000000 --- a/fee-market/src/enable_fee.rs +++ /dev/null @@ -1,23 +0,0 @@ -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait EnableFeeModule { - #[only_owner] - #[endpoint(enableFee)] - fn enable_fee(&self) { - self.fee_enabled().set(true); - } - - #[only_owner] - #[endpoint(disableFee)] - fn disable_fee(&self) { - self.fee_enabled().set(false); - } - - fn is_fee_enabled(&self) -> bool { - self.fee_enabled().get() - } - - #[storage_mapper("feeEnabledFlag")] - fn fee_enabled(&self) -> SingleValueMapper; -} diff --git a/fee-market/src/fee_common.rs b/fee-market/src/fee_common.rs deleted file mode 100644 index 0481fb8d6..000000000 --- a/fee-market/src/fee_common.rs +++ /dev/null @@ -1,15 +0,0 @@ -use error_messages::ONLY_ESDT_SAFE_CALLER; - -multiversx_sc::imports!(); - -#[multiversx_sc::module] -pub trait CommonFeeModule { - fn require_caller_esdt_safe(&self) { - let caller = self.blockchain().get_caller(); - let esdt_safe_address = self.esdt_safe_address().get(); - require!(caller == esdt_safe_address, ONLY_ESDT_SAFE_CALLER); - } - - #[storage_mapper("esdtSafeAddress")] - fn esdt_safe_address(&self) -> SingleValueMapper; -} diff --git a/fee-market/src/fee_type.rs b/fee-market/src/fee_type.rs deleted file mode 100644 index 5de139bc8..000000000 --- a/fee-market/src/fee_type.rs +++ /dev/null @@ -1,77 +0,0 @@ -use error_messages::{INVALID_FEE, INVALID_FEE_TYPE}; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -#[type_abi] -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, Clone)] -pub enum FeeType { - None, - Fixed { - token: TokenIdentifier, - per_transfer: BigUint, - per_gas: BigUint, - }, - AnyToken { - base_fee_token: TokenIdentifier, - per_transfer: BigUint, - per_gas: BigUint, - }, -} - -#[type_abi] -#[derive(TopDecode, TopEncode, NestedEncode, NestedDecode, Clone)] -pub struct FeeStruct { - pub base_token: TokenIdentifier, - pub fee_type: FeeType, -} - -#[multiversx_sc::module] -pub trait FeeTypeModule: utils::UtilsModule { - #[only_owner] - #[endpoint(setFee)] - fn set_fee(&self, fee_struct: FeeStruct) { - self.require_valid_token_id(&fee_struct.base_token); - - let token = match &fee_struct.fee_type { - FeeType::None => sc_panic!(INVALID_FEE_TYPE), - FeeType::Fixed { - token, - per_transfer: _, - per_gas: _, - } => { - require!(&fee_struct.base_token == token, INVALID_FEE); - - token - } - FeeType::AnyToken { - base_fee_token, - per_transfer: _, - per_gas: _, - } => base_fee_token, - }; - - self.require_valid_token_id(token); - self.fee_enabled().set(true); - self.token_fee(&fee_struct.base_token) - .set(fee_struct.fee_type); - } - - fn is_fee_enabled(&self) -> bool { - self.fee_enabled().get() - } - - #[only_owner] - #[endpoint(removeFee)] - fn remove_fee(&self, base_token: TokenIdentifier) { - self.token_fee(&base_token).clear(); - self.fee_enabled().set(false); - } - - #[view(getTokenFee)] - #[storage_mapper("tokenFee")] - fn token_fee(&self, token_id: &TokenIdentifier) -> SingleValueMapper>; - - #[storage_mapper("feeEnabledFlag")] - fn fee_enabled(&self) -> SingleValueMapper; -} diff --git a/fee-market/src/lib.rs b/fee-market/src/lib.rs deleted file mode 100644 index 4cca46315..000000000 --- a/fee-market/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -#![no_std] - -use fee_type::FeeStruct; - -multiversx_sc::imports!(); - -pub mod fee_common; -pub mod fee_type; -pub mod price_aggregator; -pub mod subtract_fee; - -#[multiversx_sc::contract] -pub trait FeeMarket: - fee_common::CommonFeeModule - + fee_type::FeeTypeModule - + subtract_fee::SubtractFeeModule - + price_aggregator::PriceAggregatorModule - + utils::UtilsModule -{ - #[init] - fn init(&self, esdt_safe_address: ManagedAddress, fee: Option>) { - self.require_sc_address(&esdt_safe_address); - self.esdt_safe_address().set(esdt_safe_address); - - match fee { - Some(fee_struct) => self.set_fee(fee_struct), - _ => self.fee_enabled().set(false), - } - } - - #[upgrade] - fn upgrade(&self) {} - - #[endpoint(setPriceAggregatorAddress)] - fn set_price_aggregator_address(&self, price_aggregator_address: ManagedAddress) { - self.require_sc_address(&price_aggregator_address); - self.price_aggregator_address() - .set(price_aggregator_address); - } -} diff --git a/fee-market/src/price_aggregator.rs b/fee-market/src/price_aggregator.rs deleted file mode 100644 index ca8a00135..000000000 --- a/fee-market/src/price_aggregator.rs +++ /dev/null @@ -1,93 +0,0 @@ -use error_messages::INVALID_ESDT_IDENTIFIER; - -multiversx_sc::imports!(); - -pub type AggregatorOutputType = - OptionalValue, ManagedBuffer, u64, BigUint, u8>>; - -pub const DASH_TICKER_LEN: usize = 7; - -pub struct AggregatorResult { - pub round_id: u32, - pub from: ManagedBuffer, - pub to: ManagedBuffer, - pub timestamp: u64, - pub price: BigUint, - pub decimals: u8, -} - -impl From> for AggregatorResult { - fn from(value: AggregatorOutputType) -> Self { - let opt_value = value.into_option(); - if opt_value.is_none() { - M::error_api_impl().signal_error(b"Invalid aggregator value"); - } - - let result = unsafe { opt_value.unwrap_unchecked() }; - let (round_id, from, to, timestamp, price, decimals) = result.into_tuple(); - - Self { - round_id, - from, - to, - timestamp, - price, - decimals, - } - } -} - -mod price_aggregator_proxy { - use super::AggregatorOutputType; - - multiversx_sc::imports!(); - - #[multiversx_sc::proxy] - pub trait PriceAggregatorProxy { - #[view(latestPriceFeedOptional)] - fn latest_price_feed_optional( - &self, - from: ManagedBuffer, - to: ManagedBuffer, - ) -> AggregatorOutputType; - } -} - -#[multiversx_sc::module] -pub trait PriceAggregatorModule { - fn get_safe_price( - &self, - input_token_id: &TokenIdentifier, - output_token_id: &TokenIdentifier, - ) -> BigUint { - let price_aggregator_address = self.price_aggregator_address().get(); - let input_ticker = self.get_token_ticker(input_token_id); - let output_ticker = self.get_token_ticker(output_token_id); - - let agg_output: AggregatorOutputType = self - .price_aggregator_proxy(price_aggregator_address) - .latest_price_feed_optional(input_ticker, output_ticker) - .execute_on_dest_context(); - let result = AggregatorResult::from(agg_output); - - result.price - } - - fn get_token_ticker(&self, token_id: &TokenIdentifier) -> ManagedBuffer { - require!(token_id.is_valid_esdt_identifier(), INVALID_ESDT_IDENTIFIER); - - let buffer = token_id.as_managed_buffer(); - let ticker = buffer.copy_slice(0, buffer.len() - DASH_TICKER_LEN); - - unsafe { ticker.unwrap_unchecked() } - } - - #[storage_mapper("priceAggregatorAddress")] - fn price_aggregator_address(&self) -> SingleValueMapper; - - #[proxy] - fn price_aggregator_proxy( - &self, - to: ManagedAddress, - ) -> price_aggregator_proxy::Proxy; -} diff --git a/fee-market/src/safe_price_query.rs b/fee-market/src/safe_price_query.rs deleted file mode 100644 index 79c327031..000000000 --- a/fee-market/src/safe_price_query.rs +++ /dev/null @@ -1,112 +0,0 @@ -use error_messages::{INVALID_TOKEN_USDC_PAIR_ADDRESS, INVALID_WEGLD_USDC_PAIR_ADDRESS}; - -use crate::price_aggregator; - -multiversx_sc::imports!(); - -pub const HOUR_IN_SECONDS: u64 = 60 * 60; - -pub enum PairQueryResponse { - WegldIntermediary { - token_to_wegld_pair: ManagedAddress, - wegld_to_usdc_pair: ManagedAddress, - }, - TokenToUsdc(ManagedAddress), -} - -#[multiversx_sc::module] -pub trait SafePriceQueryModule: price_aggregator::PriceAggregatorModule { - fn get_usdc_value(&self, token_id: &TokenIdentifier, amount: &BigUint) -> BigUint { - let pair_query_response = self.get_pair_to_query(token_id); - - match pair_query_response { - PairQueryResponse::WegldIntermediary { - token_to_wegld_pair, - wegld_to_usdc_pair, - } => { - let wegld_price = self.call_get_safe_price(token_to_wegld_pair, token_id, amount); - - self.call_get_safe_price(wegld_to_usdc_pair, token_id, &wegld_price) - } - - PairQueryResponse::TokenToUsdc(pair_address) => { - self.call_get_safe_price(pair_address, token_id, amount) - } - } - } - - fn call_get_safe_price( - &self, - pair_address: ManagedAddress, - token_id: &TokenIdentifier, - amount: &BigUint, - ) -> BigUint { - let safe_price_payment: EsdtTokenPayment = self - .pair_proxy(self.safe_price_pair_address().get()) - .get_safe_price_by_timestamp_offset( - pair_address, - HOUR_IN_SECONDS, - EsdtTokenPayment::new(token_id.clone(), 0, amount.clone()), - ) - .execute_on_dest_context(); - - safe_price_payment.amount - } - - fn get_pair_to_query(&self, token_id: &TokenIdentifier) -> PairQueryResponse { - let wegld_token_id = self.wegld_token_id().get(); - let usdc_token_id = self.usdc_token_id().get(); - - let token_to_wegld_pair = self.call_get_pair(token_id, &wegld_token_id); - - if !token_to_wegld_pair.is_zero() { - let wegld_to_usdc_pair = self.call_get_pair(&wegld_token_id, &usdc_token_id); - require!( - !wegld_to_usdc_pair.is_zero(), - INVALID_WEGLD_USDC_PAIR_ADDRESS - ); - - return PairQueryResponse::WegldIntermediary { - token_to_wegld_pair, - wegld_to_usdc_pair, - }; - } - - let token_to_usdc_pair = self.call_get_pair(token_id, &usdc_token_id); - - require!( - !token_to_usdc_pair.is_zero(), - INVALID_TOKEN_USDC_PAIR_ADDRESS - ); - - PairQueryResponse::TokenToUsdc(token_to_usdc_pair) - } - - fn call_get_pair( - &self, - first_token_id: &TokenIdentifier, - second_token_id: &TokenIdentifier, - ) -> ManagedAddress { - self.router_proxy(self.router_address().get()) - .get_pair(first_token_id, second_token_id) - .execute_on_dest_context() - } - - #[proxy] - fn router_proxy(&self, sc_address: ManagedAddress) -> router_mock::Proxy; - - #[proxy] - fn pair_proxy(&self, sc_address: ManagedAddress) -> pair_mock::Proxy; - - #[storage_mapper("safePricePairAddress")] - fn safe_price_pair_address(&self) -> SingleValueMapper; - - #[storage_mapper("safePricePairAddress")] - fn router_address(&self) -> SingleValueMapper; - - #[storage_mapper("usdcTokenId")] - fn usdc_token_id(&self) -> SingleValueMapper; - - #[storage_mapper("wegldTokenId")] - fn wegld_token_id(&self) -> SingleValueMapper; -} diff --git a/fee-market/src/subtract_fee.rs b/fee-market/src/subtract_fee.rs deleted file mode 100644 index 5652f3759..000000000 --- a/fee-market/src/subtract_fee.rs +++ /dev/null @@ -1,263 +0,0 @@ -use error_messages::{ - INVALID_PERCENTAGE_SUM, INVALID_TOKEN_PROVIDED_FOR_FEE, PAYMENT_DOES_NOT_COVER_FEE, - TOKEN_NOT_ACCEPTED_AS_FEE, -}; -use structs::aliases::GasLimit; - -use crate::fee_type::FeeType; - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -const TOTAL_PERCENTAGE: usize = 10_000; - -#[type_abi] -#[derive(TopEncode, TopDecode)] -pub struct FinalPayment { - pub fee: EsdtTokenPayment, - pub remaining_tokens: EsdtTokenPayment, -} - -#[derive(TopEncode, TopDecode, ManagedVecItem)] -pub struct AddressPercentagePair { - pub address: ManagedAddress, - pub percentage: usize, -} - -pub struct SubtractPaymentArguments { - pub fee_token: TokenIdentifier, - pub per_transfer: BigUint, - pub per_gas: BigUint, - pub payment: EsdtTokenPayment, - pub total_transfers: usize, - pub opt_gas_limit: OptionalValue, -} - -#[multiversx_sc::module] -pub trait SubtractFeeModule: - crate::fee_type::FeeTypeModule - + crate::fee_common::CommonFeeModule - + crate::price_aggregator::PriceAggregatorModule - + utils::UtilsModule -{ - #[only_owner] - #[endpoint(addUsersToWhitelist)] - fn add_users_to_whitelist(&self, users: MultiValueEncoded) { - self.users_whitelist().extend(users); - } - - #[only_owner] - #[endpoint(removeUsersFromWhitelist)] - fn remove_users_from_whitelist(&self, users: MultiValueEncoded) { - self.remove_items(&mut self.users_whitelist(), users); - } - - /// Percentages have to be between 0 and 10_000, and must all add up to 100% (i.e. 10_000) - #[only_owner] - #[endpoint(distributeFees)] - fn distribute_fees( - &self, - address_percentage_pairs: MultiValueEncoded>, - ) { - let percentage_total = BigUint::from(TOTAL_PERCENTAGE); - - let mut percentage_sum = 0u64; - let mut pairs = ManagedVec::>::new(); - for pair in address_percentage_pairs { - let (address, percentage) = pair.into_tuple(); - pairs.push(AddressPercentagePair { - address, - percentage, - }); - percentage_sum += percentage as u64; - } - - require!( - percentage_sum == TOTAL_PERCENTAGE as u64, - INVALID_PERCENTAGE_SUM - ); - - for token_id in self.tokens_for_fees().iter() { - let accumulated_fees = self.accumulated_fees(&token_id).get(); - if accumulated_fees == 0u32 { - continue; - } - - let mut remaining_fees = accumulated_fees.clone(); - for pair in &pairs { - let amount_to_send = - &(&accumulated_fees * &BigUint::from(pair.percentage)) / &percentage_total; - - if amount_to_send > 0 { - remaining_fees -= &amount_to_send; - - self.tx() - .to(&pair.address) - .payment(EsdtTokenPayment::new(token_id.clone(), 0, amount_to_send)) - .transfer(); - } - } - - self.accumulated_fees(&token_id).set(&remaining_fees); - } - - self.tokens_for_fees().clear(); - } - - #[payable("*")] - #[endpoint(subtractFee)] - fn subtract_fee( - &self, - original_caller: ManagedAddress, - total_transfers: usize, - opt_gas_limit: OptionalValue, - ) -> FinalPayment { - self.require_caller_esdt_safe(); - - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt().clone(); - - if !self.is_fee_enabled() || self.users_whitelist().contains(&original_caller) { - self.tx().to(&caller).payment(&payment).transfer(); - - return FinalPayment { - fee: EsdtTokenPayment::new(payment.token_identifier.clone(), 0, BigUint::zero()), - remaining_tokens: payment, - }; - } - - let final_payment = self.subtract_fee_by_type(payment, total_transfers, opt_gas_limit); - - self.tokens_for_fees() - .insert(final_payment.fee.token_identifier.clone()); - - self.accumulated_fees(&final_payment.fee.token_identifier) - .update(|amt| *amt += &final_payment.fee.amount); - - if final_payment.remaining_tokens.amount > 0 { - self.tx() - .to(&original_caller) - .payment(&final_payment.remaining_tokens) - .transfer(); - } - - final_payment - } - - fn subtract_fee_by_type( - &self, - payment: EsdtTokenPayment, - total_transfers: usize, - opt_gas_limit: OptionalValue, - ) -> FinalPayment { - let fee_type = self.token_fee(&payment.token_identifier).get(); - match fee_type { - FeeType::None => sc_panic!(TOKEN_NOT_ACCEPTED_AS_FEE), - FeeType::Fixed { - token, - per_transfer, - per_gas, - } => { - let args = SubtractPaymentArguments { - fee_token: token, - per_transfer, - per_gas, - payment, - total_transfers, - opt_gas_limit, - }; - self.subtract_fee_same_token(args) - } - FeeType::AnyToken { - base_fee_token, - per_transfer, - per_gas, - } => { - let args = SubtractPaymentArguments { - fee_token: base_fee_token.clone(), - per_transfer, - per_gas, - payment: payment.clone(), - total_transfers, - opt_gas_limit, - }; - - if base_fee_token == payment.token_identifier { - self.subtract_fee_same_token(args) - } else { - self.subtract_fee_any_token(args) - } - } - } - } - - fn subtract_fee_same_token( - &self, - args: SubtractPaymentArguments, - ) -> FinalPayment { - require!( - args.payment.token_identifier == args.fee_token, - INVALID_TOKEN_PROVIDED_FOR_FEE - ); - - let mut total_fee = args.per_transfer * args.total_transfers as u32; - if let OptionalValue::Some(gas_limit) = args.opt_gas_limit { - total_fee += args.per_gas * gas_limit; - } - - let mut payment = args.payment; - require!(total_fee <= payment.amount, PAYMENT_DOES_NOT_COVER_FEE); - - payment.amount -= &total_fee; - - FinalPayment { - fee: EsdtTokenPayment::new(payment.token_identifier.clone(), 0, total_fee), - remaining_tokens: payment, - } - } - - fn subtract_fee_any_token( - &self, - mut args: SubtractPaymentArguments, - ) -> FinalPayment { - let input_payment = args.payment.clone(); - let payment_amount_in_fee_token = - self.get_safe_price(&args.payment.token_identifier, &args.fee_token); - args.payment = EsdtTokenPayment::new( - args.fee_token.clone(), - 0, - payment_amount_in_fee_token.clone(), - ); - - let final_payment = self.subtract_fee_same_token(args); - if final_payment.remaining_tokens.amount == 0 { - return final_payment; - } - - // Example: Total cost 1500 RIDE. - // User pays 100 EGLD, which by asking the pair you found out is 2000 RIDE - // Then the cost for the user is (1500 RIDE * 100 EGLD / 2000 RIDE = 75 EGLD) - let fee_amount_in_user_token = - &final_payment.fee.amount * &input_payment.amount / &payment_amount_in_fee_token; - let remaining_amount = input_payment.amount - fee_amount_in_user_token; - - FinalPayment { - fee: final_payment.fee, - remaining_tokens: EsdtTokenPayment::new( - input_payment.token_identifier, - 0, - remaining_amount, - ), - } - } - - #[view(getUsersWhitelist)] - #[storage_mapper("usersWhitelist")] - fn users_whitelist(&self) -> UnorderedSetMapper; - - #[storage_mapper("accFees")] - fn accumulated_fees(&self, token_id: &TokenIdentifier) -> SingleValueMapper; - - #[storage_mapper("tokensForFees")] - fn tokens_for_fees(&self) -> UnorderedSetMapper; -} diff --git a/fee-market/tests/fee_market_blackbox_setup.rs b/fee-market/tests/fee_market_blackbox_setup.rs deleted file mode 100644 index 50df82bd4..000000000 --- a/fee-market/tests/fee_market_blackbox_setup.rs +++ /dev/null @@ -1,235 +0,0 @@ -use multiversx_sc::{ - imports::OptionalValue, - types::{ - BigUint, EsdtTokenPayment, ManagedVec, MultiValueEncoded, TestAddress, TestSCAddress, - TestTokenIdentifier, - }, -}; -use multiversx_sc_scenario::{ - api::StaticApi, imports::MxscPath, ReturnsHandledOrError, ScenarioTxRun, ScenarioWorld, -}; -use proxies::fee_market_proxy::{FeeMarketProxy, FeeStruct, FeeType}; - -const FEE_MARKET_CODE_PATH: MxscPath = MxscPath::new("output/fee-market.mxsc.json"); -const FEE_MARKET_ADDRESS: TestSCAddress = TestSCAddress::new("fee-market"); - -pub const ESDT_SAFE_ADDRESS: TestSCAddress = TestSCAddress::new("esdt-safe"); -const ESDT_SAFE_CODE_PATH: MxscPath = MxscPath::new("../esdt-safe/output/esdt-safe.mxsc.json"); - -const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); -pub const USER_ADDRESS: TestAddress = TestAddress::new("user"); - -pub const TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("TDK-123456"); -pub const DIFFERENT_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WRONG-123456"); -const ANOTHER_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("ANOTHER-123456"); -pub const WRONG_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WRONG-TOKEN"); - -pub fn world() -> ScenarioWorld { - let mut blockchain = ScenarioWorld::new(); - - blockchain.register_contract(FEE_MARKET_CODE_PATH, fee_market::ContractBuilder); - - blockchain -} - -pub struct FeeMarketTestState { - pub world: ScenarioWorld, -} - -impl FeeMarketTestState { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - let mut world = world(); - - world - .account(OWNER_ADDRESS) - .esdt_balance(TOKEN_ID, 1000) - .nonce(1); - - world - .account(USER_ADDRESS) - .esdt_balance(TOKEN_ID, 1000) - .nonce(1); - - world - .account(ESDT_SAFE_ADDRESS) - .code(ESDT_SAFE_CODE_PATH) - .esdt_balance(TOKEN_ID, 1000) - .esdt_balance(DIFFERENT_TOKEN_ID, 1000) - .esdt_balance(ANOTHER_TOKEN_ID, 1000) - .nonce(1); - - Self { world } - } - - pub fn deploy_fee_market(&mut self) -> &mut Self { - let fee = FeeStruct { - base_token: TOKEN_ID.to_token_identifier(), - fee_type: FeeType::Fixed { - token: TOKEN_ID.to_token_identifier(), - per_transfer: BigUint::from(100u64), - per_gas: BigUint::from(0u64), - }, - }; - - self.world - .tx() - .from(OWNER_ADDRESS) - .typed(FeeMarketProxy) - .init(ESDT_SAFE_ADDRESS, Option::Some(fee)) - .code(FEE_MARKET_CODE_PATH) - .new_address(FEE_MARKET_ADDRESS) - .run(); - - self - } - - pub fn substract_fee(&mut self, payment_wanted: &str, expected_result: Option<&str>) { - let payment: EsdtTokenPayment = match payment_wanted { - "Correct" => { - EsdtTokenPayment::new(TOKEN_ID.to_token_identifier(), 0u64, BigUint::from(200u64)) - } - "InvalidToken" => EsdtTokenPayment::new( - DIFFERENT_TOKEN_ID.to_token_identifier::(), - 0u64, - BigUint::from(10u64), - ), - "AnyToken" => EsdtTokenPayment::new( - ANOTHER_TOKEN_ID.to_token_identifier(), - 0u64, - BigUint::from(10u64), - ), - "Less than fee" => { - EsdtTokenPayment::new(TOKEN_ID.to_token_identifier(), 0u64, BigUint::from(0u64)) - } - _ => { - panic!("Invalid payment wanted"); - } - }; - - let response = self - .world - .tx() - .from(ESDT_SAFE_ADDRESS) - .to(FEE_MARKET_ADDRESS) - .typed(FeeMarketProxy) - .subtract_fee(USER_ADDRESS, 1u8, OptionalValue::Some(30u64)) - .payment(payment) - .returns(ReturnsHandledOrError::new()) - .run(); - - match response { - Ok(_) => assert!( - expected_result.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => assert_eq!(expected_result, Some(error.message.as_str())), - }; - } - - pub fn remove_fee(&mut self) { - self.world - .tx() - .from(OWNER_ADDRESS) - .to(FEE_MARKET_ADDRESS) - .typed(FeeMarketProxy) - .remove_fee(TOKEN_ID) - .run(); - } - - pub fn set_fee( - &mut self, - token_id: TestTokenIdentifier, - fee_type: &str, - expected_result: Option<&str>, - ) { - let fee_struct: FeeStruct = match fee_type { - "None" => { - let fee_type = FeeType::None; - FeeStruct { - base_token: token_id.to_token_identifier(), - fee_type, - } - } - "Fixed" => { - let fee_type = FeeType::Fixed { - token: TOKEN_ID.to_token_identifier(), - per_transfer: BigUint::from(10u8), - per_gas: BigUint::from(10u8), - }; - FeeStruct { - base_token: token_id.to_token_identifier(), - fee_type, - } - } - "AnyToken" => { - let fee_type = FeeType::AnyToken { - base_fee_token: DIFFERENT_TOKEN_ID.to_token_identifier(), - per_transfer: BigUint::from(10u8), - per_gas: BigUint::from(10u8), - }; - FeeStruct { - base_token: token_id.to_token_identifier(), - fee_type, - } - } - "AnyTokenWrong" => { - let fee_type = FeeType::AnyToken { - base_fee_token: WRONG_TOKEN_ID.to_token_identifier(), - per_transfer: BigUint::from(10u8), - per_gas: BigUint::from(10u8), - }; - FeeStruct { - base_token: token_id.to_token_identifier(), - fee_type, - } - } - _ => { - panic!("Invalid fee type"); - } - }; - - let response = self - .world - .tx() - .from(OWNER_ADDRESS) - .to(FEE_MARKET_ADDRESS) - .typed(FeeMarketProxy) - .set_fee(fee_struct) - .returns(ReturnsHandledOrError::new()) - .run(); - - match response { - Ok(_) => assert!( - expected_result.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => assert_eq!(expected_result, Some(error.message.as_str())), - }; - } - - pub fn add_users_to_whitelist(&mut self) { - let mut users_vec = ManagedVec::new(); - users_vec.push(USER_ADDRESS.to_managed_address()); - let users = MultiValueEncoded::from(users_vec); - self.world - .tx() - .from(OWNER_ADDRESS) - .to(FEE_MARKET_ADDRESS) - .typed(FeeMarketProxy) - .add_users_to_whitelist(users) - .run(); - } - - pub fn check_balance_sc(&mut self, address: TestSCAddress, expected_balance: u64) { - self.world - .check_account(address) - .esdt_balance(TOKEN_ID, expected_balance); - } - - pub fn check_account(&mut self, address: TestAddress, expected_balance: u64) { - self.world - .check_account(address) - .esdt_balance(TOKEN_ID, expected_balance); - } -} diff --git a/fee-market/tests/fee_market_blackbox_test.rs b/fee-market/tests/fee_market_blackbox_test.rs deleted file mode 100644 index 7df24c3e4..000000000 --- a/fee-market/tests/fee_market_blackbox_test.rs +++ /dev/null @@ -1,91 +0,0 @@ -use error_messages::{ - INVALID_FEE, INVALID_FEE_TYPE, INVALID_TOKEN_ID, PAYMENT_DOES_NOT_COVER_FEE, - TOKEN_NOT_ACCEPTED_AS_FEE, -}; -use fee_market_blackbox_setup::*; - -mod fee_market_blackbox_setup; - -#[test] -fn test_deploy_fee_market() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); -} - -#[test] -fn test_set_fee_wrong_params() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - - state.set_fee(WRONG_TOKEN_ID, "Fixed", Some(INVALID_TOKEN_ID)); - - state.set_fee(TOKEN_ID, "None", Some(INVALID_FEE_TYPE)); - - state.set_fee(DIFFERENT_TOKEN_ID, "Fixed", Some(INVALID_FEE)); - - state.set_fee(TOKEN_ID, "AnyTokenWrong", Some(INVALID_TOKEN_ID)); -} - -#[test] -fn test_substract_fee_no_fee() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - state.remove_fee(); - - state.substract_fee("Correct", None); - - state.check_balance_sc(ESDT_SAFE_ADDRESS, 1000); - state.check_account(USER_ADDRESS, 1000); -} - -#[test] -fn test_substract_fee_whitelisted() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - state.add_users_to_whitelist(); - - state.substract_fee("Correct", None); - - state.check_balance_sc(ESDT_SAFE_ADDRESS, 1000); - state.check_account(USER_ADDRESS, 1000); -} - -#[test] -fn test_substract_fee_invalid_payment_token() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - - state.substract_fee("InvalidToken", Some(TOKEN_NOT_ACCEPTED_AS_FEE)); - - state.check_balance_sc(ESDT_SAFE_ADDRESS, 1000); - state.check_account(USER_ADDRESS, 1000); -} - -#[test] -fn test_substract_fixed_fee_payment_not_covered() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - - state.substract_fee("Less than fee", Some(PAYMENT_DOES_NOT_COVER_FEE)); - - state.check_balance_sc(ESDT_SAFE_ADDRESS, 1000); - state.check_account(USER_ADDRESS, 1000); -} - -#[test] -fn test_substract_fee_fixed_payment_bigger_than_fee() { - let mut state = FeeMarketTestState::new(); - - state.deploy_fee_market(); - - state.substract_fee("Correct", None); - - state.check_balance_sc(ESDT_SAFE_ADDRESS, 800); - state.check_account(USER_ADDRESS, 1100); -} diff --git a/fee-market/wasm-fee-marker-full/Cargo.lock b/fee-market/wasm-fee-marker-full/Cargo.lock deleted file mode 100644 index 3f97cdef2..000000000 --- a/fee-market/wasm-fee-marker-full/Cargo.lock +++ /dev/null @@ -1,248 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "fee-marker-full-wasm" -version = "0.0.0" -dependencies = [ - "fee-market", - "multiversx-sc-wasm-adapter", -] - -[[package]] -name = "fee-market" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/fee-market/wasm-fee-market-view/Cargo.lock b/fee-market/wasm-fee-market-view/Cargo.lock deleted file mode 100644 index 04cdf4a59..000000000 --- a/fee-market/wasm-fee-market-view/Cargo.lock +++ /dev/null @@ -1,248 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "fee-market" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "fee-market-view-wasm" -version = "0.0.0" -dependencies = [ - "fee-market", - "multiversx-sc-wasm-adapter", -] - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/fee-market/wasm-fee-market-view/src/lib.rs b/fee-market/wasm-fee-market-view/src/lib.rs deleted file mode 100644 index 86d59962e..000000000 --- a/fee-market/wasm-fee-market-view/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 0 -// Async Callback (empty): 1 -// Total number of exported functions: 2 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::external_view_init! {} - -multiversx_sc_wasm_adapter::external_view_endpoints! { - fee_market - ( - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/fee-market/wasm-fee-market/Cargo.lock b/fee-market/wasm-fee-market/Cargo.lock deleted file mode 100644 index 89134a332..000000000 --- a/fee-market/wasm-fee-market/Cargo.lock +++ /dev/null @@ -1,248 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "fee-market" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "fee-market-wasm" -version = "0.0.0" -dependencies = [ - "fee-market", - "multiversx-sc-wasm-adapter", -] - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/header-verifier/Cargo.toml b/header-verifier/Cargo.toml index 350478fbc..64c8f795b 100644 --- a/header-verifier/Cargo.toml +++ b/header-verifier/Cargo.toml @@ -9,7 +9,7 @@ publish = false path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.57.1" +version = "0.63.0" [dependencies.structs] path = "../common/structs" @@ -17,9 +17,18 @@ path = "../common/structs" [dependencies.proxies] path = "../common/proxies" +[dependencies.setup-phase] +path = "../common/setup-phase" + [dependencies.error-messages] path = "../common/error-messages" +[dependencies.custom-events] +path = "../common/custom-events" + +[dependencies.common-utils] +path = "../common/common-utils" + [dependencies.cross-chain] path = "../common/cross-chain" @@ -27,4 +36,5 @@ path = "../common/cross-chain" path = "../common/common-test-setup" [dev-dependencies.multiversx-sc-scenario] -version = "0.57.1" +version = "0.63.0" +features = ["bls"] diff --git a/header-verifier/README.md b/header-verifier/README.md new file mode 100644 index 000000000..0e8829677 --- /dev/null +++ b/header-verifier/README.md @@ -0,0 +1,9 @@ +# Header Verifier Contract + +Holds the active validator set and verifies BLS-aggregated signatures for bridge operation bundles (`hashOfHashes`). It locks operation hashes to prevent double execution and signals completion back to the calling contracts. + +- **Setup:** `completeSetupPhase` fetches the genesis validator set from Chain Config and marks setup done. Only proceeds once Chain Config completed its own setup. +- **Registering operations:** `registerBridgeOps(signature, hashOfHashes, bitmap, epoch, operations)` checks setup, validates the signature against the current epoch’s validator keys, ensures the bundle hash matches, and marks each operation hash as `NotLocked`. +- **Validator rotation:** `changeValidatorSet` uses the previous epoch’s signatures to register a new validator key set (by IDs stored in Chain Config). Older epochs are pruned as `MAX_STORED_EPOCHS` is exceeded. +- **Execution locking:** `lockOperationHash(hashOfHashes, opHash, nonce)` marks an operation as `Locked` (enforcing the expected nonce). `removeExecutedHash` clears the status after completion; calling contracts typically use the wrapper in `complete_operation`. +- **Callers:** The contract expects to be the owner of MultiversX ESDT Safe and Fee Market so it can authorize bridge operations. Sovereign Forge arranges this via Chain Factory during setup. diff --git a/header-verifier/meta/Cargo.toml b/header-verifier/meta/Cargo.toml index b9c669145..0b49caa3c 100644 --- a/header-verifier/meta/Cargo.toml +++ b/header-verifier/meta/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.57.1" +version = "0.63.0" default-features = false diff --git a/header-verifier/sc-config.toml b/header-verifier/sc-config.toml index cd5f9ce16..8275d6aa0 100644 --- a/header-verifier/sc-config.toml +++ b/header-verifier/sc-config.toml @@ -1,16 +1,2 @@ -[contracts.main] -name = "header-verifier" - -[contracts.full] -name = "header-verifier-full" -add-unlabelled = true -add-labels = ["header-verifier-external-view"] - -[contracts.view] -name = "header-verifier-view" -external-view = true -add-unlabelled = false -add-labels = ["header-verifier-external-view"] - [[proxy]] path = "../common/proxies/src/header_verifier_proxy.rs" diff --git a/header-verifier/src/checks.rs b/header-verifier/src/checks.rs new file mode 100644 index 000000000..efea786cb --- /dev/null +++ b/header-verifier/src/checks.rs @@ -0,0 +1,48 @@ +use error_messages::CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE; +use structs::OperationHashStatus; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait HeaderVerifierChecksModule: + crate::storage::HeaderVerifierStorageModule + + custom_events::CustomEventsModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule +{ + fn is_bls_pub_keys_empty(&self, epoch: u64) -> bool { + self.bls_pub_keys(epoch).is_empty() + } + + fn require_chain_config_setup_complete(&self, chain_config_address: &ManagedAddress) { + require!( + self.chain_config_setup_phase_complete(chain_config_address.clone()) + .get(), + CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE + ); + } + + fn is_hash_of_hashes_registered( + &self, + hash_of_hashes: &ManagedBuffer, + history_mapper: &UnorderedSetMapper, + ) -> bool { + history_mapper.contains(hash_of_hashes) + } + + fn is_hash_status_mapper_empty( + &self, + hash_status_mapper: &SingleValueMapper, + ) -> bool { + hash_status_mapper.is_empty() + } + + fn are_hash_of_hashes_matching( + &self, + hash_of_hashes: &ManagedBuffer, + computed_hash_of_hashes: &ManagedBuffer, + ) -> bool { + computed_hash_of_hashes.eq(hash_of_hashes) + } +} diff --git a/header-verifier/src/header_utils.rs b/header-verifier/src/header_utils.rs new file mode 100644 index 000000000..6a346de06 --- /dev/null +++ b/header-verifier/src/header_utils.rs @@ -0,0 +1,129 @@ +use error_messages::{ + BLS_KEY_NOT_REGISTERED, CHAIN_CONFIG_NOT_DEPLOYED, HASH_OF_HASHES_DOES_NOT_MATCH, +}; +use structs::forge::ScArray; + +use crate::checks; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub const MAX_STORED_EPOCHS: u64 = 3; + +#[multiversx_sc::module] +pub trait HeaderVerifierUtilsModule: + super::storage::HeaderVerifierStorageModule + + checks::HeaderVerifierChecksModule + + custom_events::CustomEventsModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule +{ + fn calculate_and_check_transfers_hashes( + &self, + transfers_hash: &ManagedBuffer, + transfers_data: MultiValueEncoded, + ) -> Result<(), ManagedBuffer> { + let mut transfers_hashes = ManagedBuffer::new(); + for transfer in transfers_data { + transfers_hashes.append(&transfer); + } + + let hash_of_hashes_sha256 = self.crypto().sha256(&transfers_hashes); + let hash_of_hashes = hash_of_hashes_sha256.as_managed_buffer(); + + if !self.are_hash_of_hashes_matching(transfers_hash, hash_of_hashes) { + return Err(HASH_OF_HASHES_DOES_NOT_MATCH.into()); + } + + Ok(()) + } + + fn get_chain_config_address(&self) -> ManagedAddress { + self.sovereign_contracts() + .iter() + .find(|sc| sc.id == ScArray::ChainConfig) + .unwrap_or_else(|| sc_panic!(CHAIN_CONFIG_NOT_DEPLOYED)) + .address + } + + fn get_approving_validators( + &self, + epoch: u64, + bls_keys_bitmap: &ManagedBuffer, + bls_keys_length: usize, + ) -> ManagedVec { + let mut padded_bitmap_byte_array = [0u8; 1024]; + let bytes_count = bls_keys_bitmap + .load_to_byte_array(&mut padded_bitmap_byte_array) + .len(); + + let bitmap_byte_array = &padded_bitmap_byte_array[..bytes_count]; + + let mut approving_validators_bls_keys: ManagedVec = + ManagedVec::new(); + + let bls_keys_from_storage: ManagedVec = + self.bls_pub_keys(epoch).iter().collect(); + + let mut validator_index: usize = 0; + + 'outer: for byte in bitmap_byte_array { + for bit in 0..8 { + if validator_index >= bls_keys_length { + break 'outer; + } + + if (byte >> bit) & 1 == 1 { + approving_validators_bls_keys + .push(bls_keys_from_storage.get(validator_index).clone()); + } + + validator_index += 1; + } + } + + approving_validators_bls_keys + } + + fn get_bls_keys_by_id( + &self, + ids: MultiValueEncoded>, + ) -> Result, &str> { + let mut bls_keys = ManagedVec::new(); + + for id in ids.into_iter() { + let id = self.bls_keys_map(self.get_chain_config_address()).get(&id); + if id.is_none() { + return Err(BLS_KEY_NOT_REGISTERED); + } + bls_keys.push(id.unwrap()); + } + + Ok(bls_keys) + } + + fn verify_bls( + &self, + epoch: u64, + signature: &ManagedBuffer, + hash_of_hashes: &ManagedBuffer, + bls_keys_bitmap: ManagedBuffer, + bls_pub_keys_len: usize, + ) { + let approving_validators = + self.get_approving_validators(epoch, &bls_keys_bitmap, bls_pub_keys_len); + + self.crypto().verify_bls_aggregated_signature( + &approving_validators, + hash_of_hashes, + signature, + ); + } + + fn is_caller_from_current_sovereign(&self) -> bool { + let caller = self.blockchain().get_caller(); + self.sovereign_contracts() + .iter() + .any(|sc| sc.address == caller) + } +} diff --git a/header-verifier/src/lib.rs b/header-verifier/src/lib.rs index 71e8e8904..319fc3d88 100644 --- a/header-verifier/src/lib.rs +++ b/header-verifier/src/lib.rs @@ -1,195 +1,48 @@ #![no_std] - -use error_messages::{ - BLS_SIGNATURE_NOT_VALID, CURRENT_OPERATION_ALREADY_IN_EXECUTION, - CURRENT_OPERATION_NOT_REGISTERED, HASH_OF_HASHES_DOES_NOT_MATCH, NO_ESDT_SAFE_ADDRESS, - ONLY_ESDT_SAFE_CALLER, OUTGOING_TX_HASH_ALREADY_REGISTERED, -}; -use multiversx_sc::codec; -use multiversx_sc::proxy_imports::{TopDecode, TopEncode}; +use structs::forge::ContractInfo; +pub mod checks; +pub mod header_utils; +pub mod operations; +pub mod storage; multiversx_sc::imports!(); -#[derive(TopEncode, TopDecode, PartialEq)] -pub enum OperationHashStatus { - NotLocked = 1, - Locked, -} - #[multiversx_sc::contract] -pub trait Headerverifier: cross_chain::events::EventsModule { +pub trait Headerverifier: + storage::HeaderVerifierStorageModule + + header_utils::HeaderVerifierUtilsModule + + operations::HeaderVerifierOperationsModule + + checks::HeaderVerifierChecksModule + + custom_events::CustomEventsModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule +{ #[init] - fn init(&self) {} - - #[only_owner] - #[endpoint(registerBlsPubKeys)] - fn register_bls_pub_keys(&self, bls_pub_keys: MultiValueEncoded) { - self.bls_pub_keys().clear(); - self.bls_pub_keys().extend(bls_pub_keys); + fn init(&self, sovereign_contracts: MultiValueEncoded>) { + self.sovereign_contracts().extend(sovereign_contracts); } #[upgrade] fn upgrade(&self) {} - #[endpoint(registerBridgeOps)] - fn register_bridge_operations( - &self, - signature: ManagedBuffer, - bridge_operations_hash: ManagedBuffer, - _pub_keys_bitmap: ManagedBuffer, - _epoch: ManagedBuffer, - operations_hashes: MultiValueEncoded, - ) { - let mut hash_of_hashes_history_mapper = self.hash_of_hashes_history(); - - require!( - !hash_of_hashes_history_mapper.contains(&bridge_operations_hash), - OUTGOING_TX_HASH_ALREADY_REGISTERED - ); - - let is_bls_valid = self.verify_bls(&signature, &bridge_operations_hash); - require!(is_bls_valid, BLS_SIGNATURE_NOT_VALID); - - self.calculate_and_check_transfers_hashes( - &bridge_operations_hash, - operations_hashes.clone(), - ); - - for operation_hash in operations_hashes { - self.operation_hash_status(&bridge_operations_hash, &operation_hash) - .set(OperationHashStatus::NotLocked); - } - - hash_of_hashes_history_mapper.insert(bridge_operations_hash); - } - - #[endpoint(changeValidatorSet)] - fn change_validator_set( - &self, - signature: ManagedBuffer, - bridge_operations_hash: ManagedBuffer, - operation_hash: ManagedBuffer, - _pub_keys_bitmap: ManagedBuffer, - _epoch: ManagedBuffer, - _pub_keys_id: MultiValueEncoded, - ) { - let mut hash_of_hashes_history_mapper = self.hash_of_hashes_history(); - - require!( - !hash_of_hashes_history_mapper.contains(&bridge_operations_hash), - OUTGOING_TX_HASH_ALREADY_REGISTERED - ); - - let is_bls_valid = self.verify_bls(&signature, &bridge_operations_hash); - require!(is_bls_valid, BLS_SIGNATURE_NOT_VALID); - - let mut operations_hashes = MultiValueEncoded::new(); - operations_hashes.push(operation_hash.clone()); - self.calculate_and_check_transfers_hashes( - &bridge_operations_hash, - operations_hashes.clone(), - ); - - // TODO change validators set - - hash_of_hashes_history_mapper.insert(bridge_operations_hash.clone()); - self.execute_bridge_operation_event(&bridge_operations_hash, &operation_hash); - } - #[only_owner] - #[endpoint(setEsdtSafeAddress)] - fn set_esdt_safe_address(&self, esdt_safe_address: ManagedAddress) { - self.esdt_safe_address().set(esdt_safe_address); - } - - #[endpoint(removeExecutedHash)] - fn remove_executed_hash(&self, hash_of_hashes: &ManagedBuffer, operation_hash: &ManagedBuffer) { - self.require_caller_esdt_safe(); - - self.operation_hash_status(hash_of_hashes, operation_hash) - .clear(); - } - - #[endpoint(lockOperationHash)] - fn lock_operation_hash(&self, hash_of_hashes: ManagedBuffer, operation_hash: ManagedBuffer) { - self.require_caller_esdt_safe(); - - let operation_hash_status_mapper = - self.operation_hash_status(&hash_of_hashes, &operation_hash); - - require!( - !operation_hash_status_mapper.is_empty(), - CURRENT_OPERATION_NOT_REGISTERED - ); - - let is_hash_in_execution = operation_hash_status_mapper.get(); - match is_hash_in_execution { - OperationHashStatus::NotLocked => { - operation_hash_status_mapper.set(OperationHashStatus::Locked) - } - OperationHashStatus::Locked => { - sc_panic!(CURRENT_OPERATION_ALREADY_IN_EXECUTION) - } + #[endpoint(completeSetupPhase)] + fn complete_setup_phase(&self) { + if self.is_setup_phase_complete() { + return; } - } + let chain_config_address = self.get_chain_config_address(); - fn require_caller_esdt_safe(&self) { - let esdt_safe_mapper = self.esdt_safe_address(); + self.require_chain_config_setup_complete(&chain_config_address); - require!(!esdt_safe_mapper.is_empty(), NO_ESDT_SAFE_ADDRESS); + let genesis_validators: ManagedVec = self + .bls_keys_map(chain_config_address) + .iter() + .map(|(_, bls_key)| bls_key) + .collect(); - let caller = self.blockchain().get_caller(); - require!(caller == esdt_safe_mapper.get(), ONLY_ESDT_SAFE_CALLER); - } - - fn calculate_and_check_transfers_hashes( - &self, - transfers_hash: &ManagedBuffer, - transfers_data: MultiValueEncoded, - ) { - let mut transfers_hashes = ManagedBuffer::new(); - for transfer in transfers_data { - transfers_hashes.append(&transfer); - } + self.bls_pub_keys(0).extend(genesis_validators); - let hash_of_hashes_sha256 = self.crypto().sha256(&transfers_hashes); - let hash_of_hashes = hash_of_hashes_sha256.as_managed_buffer(); - - require!( - transfers_hash.eq(hash_of_hashes), - HASH_OF_HASHES_DOES_NOT_MATCH - ); - } - - // TODO - fn verify_bls( - &self, - _signature: &ManagedBuffer, - _bridge_operations_hash: &ManagedBuffer, - ) -> bool { - true - } - - fn is_signature_count_valid(&self, pub_keys_count: usize) -> bool { - let total_bls_pub_keys = self.bls_pub_keys().len(); - let minimum_signatures = 2 * total_bls_pub_keys / 3; - - pub_keys_count > minimum_signatures + self.setup_phase_complete().set(true); } - - #[storage_mapper("blsPubKeys")] - fn bls_pub_keys(&self) -> SetMapper; - - #[storage_mapper("operationHashStatus")] - fn operation_hash_status( - &self, - hash_of_hashes: &ManagedBuffer, - operation_hash: &ManagedBuffer, - ) -> SingleValueMapper; - - #[storage_mapper("hashOfHashesHistory")] - fn hash_of_hashes_history(&self) -> UnorderedSetMapper; - - #[storage_mapper("esdtSafeAddress")] - fn esdt_safe_address(&self) -> SingleValueMapper; } diff --git a/header-verifier/src/operations.rs b/header-verifier/src/operations.rs new file mode 100644 index 000000000..5f4e236c1 --- /dev/null +++ b/header-verifier/src/operations.rs @@ -0,0 +1,228 @@ +use error_messages::{ + CALLER_NOT_FROM_CURRENT_SOVEREIGN, CURRENT_OPERATION_ALREADY_IN_EXECUTION, + CURRENT_OPERATION_NOT_REGISTERED, HASH_OF_HASHES_DOES_NOT_MATCH, INCORRECT_OPERATION_NONCE, + INVALID_EPOCH, NO_VALIDATORS_FOR_GIVEN_EPOCH, NO_VALIDATORS_FOR_PREVIOUS_EPOCH, + OUTGOING_TX_HASH_ALREADY_REGISTERED, SETUP_PHASE_NOT_COMPLETED, + VALIDATORS_ALREADY_REGISTERED_IN_EPOCH, +}; +use structs::{aliases::TxNonce, OperationHashStatus}; + +use crate::{ + checks, + header_utils::{self, MAX_STORED_EPOCHS}, + storage, +}; +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait HeaderVerifierOperationsModule: + header_utils::HeaderVerifierUtilsModule + + storage::HeaderVerifierStorageModule + + checks::HeaderVerifierChecksModule + + custom_events::CustomEventsModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule +{ + #[endpoint(registerBridgeOps)] + fn register_bridge_operations( + &self, + signature: ManagedBuffer, + hash_of_hashes: ManagedBuffer, + pub_keys_bitmap: ManagedBuffer, + epoch: u64, + operations_hashes: MultiValueEncoded, + ) { + require!(self.is_setup_phase_complete(), SETUP_PHASE_NOT_COMPLETED); + require!( + !self.is_bls_pub_keys_empty(epoch), + NO_VALIDATORS_FOR_GIVEN_EPOCH + ); + + let bls_pub_keys_mapper = self.bls_pub_keys(epoch); + let mut hash_of_hashes_history_mapper = self.hash_of_hashes_history(); + + require!( + !self.is_hash_of_hashes_registered(&hash_of_hashes, &hash_of_hashes_history_mapper), + OUTGOING_TX_HASH_ALREADY_REGISTERED + ); + require!( + self.calculate_and_check_transfers_hashes(&hash_of_hashes, operations_hashes.clone()) + .is_ok(), + HASH_OF_HASHES_DOES_NOT_MATCH + ); + + self.verify_bls( + epoch, + &signature, + &hash_of_hashes, + pub_keys_bitmap, + bls_pub_keys_mapper.len(), + ); + + for operation_hash in operations_hashes { + self.operation_hash_status(&hash_of_hashes, &operation_hash) + .set(OperationHashStatus::NotLocked); + } + + hash_of_hashes_history_mapper.insert(hash_of_hashes); + } + + #[endpoint(changeValidatorSet)] + fn change_validator_set( + &self, + signature: ManagedBuffer, + hash_of_hashes: ManagedBuffer, + operation_hash: ManagedBuffer, + pub_keys_bitmap: ManagedBuffer, + epoch: u64, + pub_keys_id: MultiValueEncoded>, + ) { + if !self.is_setup_phase_complete() { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + + return; + } + + if epoch == 0 { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(INVALID_EPOCH.into()), + ); + + return; + } + + if !self.is_bls_pub_keys_empty(epoch) { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(VALIDATORS_ALREADY_REGISTERED_IN_EPOCH.into()), + ); + + return; + } + + if self.is_bls_pub_keys_empty(epoch - 1) { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(NO_VALIDATORS_FOR_PREVIOUS_EPOCH.into()), + ); + + return; + } + + let bls_keys_previous_epoch = self.bls_pub_keys(epoch - 1); + let mut hash_of_hashes_history_mapper = self.hash_of_hashes_history(); + if self.is_hash_of_hashes_registered(&hash_of_hashes, &hash_of_hashes_history_mapper) { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(OUTGOING_TX_HASH_ALREADY_REGISTERED.into()), + ); + + return; + } + + let mut operations_hashes = MultiValueEncoded::new(); + operations_hashes.push(operation_hash.clone()); + + if let Err(error_message) = + self.calculate_and_check_transfers_hashes(&hash_of_hashes, operations_hashes.clone()) + { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(error_message), + ); + + return; + } + + self.verify_bls( + epoch - 1, // Use the validator signatures from the last epoch + &signature, + &hash_of_hashes, + pub_keys_bitmap, + bls_keys_previous_epoch.len(), + ); + + if epoch >= MAX_STORED_EPOCHS && !self.bls_pub_keys(epoch - MAX_STORED_EPOCHS).is_empty() { + self.bls_pub_keys(epoch - MAX_STORED_EPOCHS).clear(); + } + + match self.get_bls_keys_by_id(pub_keys_id) { + Ok(new_bls_keys) => self.bls_pub_keys(epoch).extend(new_bls_keys), + Err(error_message) => { + self.execute_bridge_operation_event( + &hash_of_hashes, + &operation_hash, + Some(error_message.into()), + ); + + return; + } + } + + hash_of_hashes_history_mapper.insert(hash_of_hashes.clone()); + self.execute_bridge_operation_event(&hash_of_hashes, &operation_hash, None); + } + + #[endpoint(removeExecutedHash)] + fn remove_executed_hash( + &self, + hash_of_hashes: &ManagedBuffer, + operation_hash: &ManagedBuffer, + ) -> OptionalValue { + if !self.is_caller_from_current_sovereign() { + return OptionalValue::Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN.into()); + } + + self.operation_hash_status(hash_of_hashes, operation_hash) + .clear(); + + OptionalValue::None + } + + #[endpoint(lockOperationHash)] + fn lock_operation_hash( + &self, + hash_of_hashes: ManagedBuffer, + operation_hash: ManagedBuffer, + operation_nonce: TxNonce, + ) -> OptionalValue { + if !self.is_caller_from_current_sovereign() { + return OptionalValue::Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN.into()); + } + + let operation_hash_status_mapper = + self.operation_hash_status(&hash_of_hashes, &operation_hash); + + if self.is_hash_status_mapper_empty(&operation_hash_status_mapper) { + return OptionalValue::Some(CURRENT_OPERATION_NOT_REGISTERED.into()); + } + + let is_hash_in_execution = operation_hash_status_mapper.get(); + match is_hash_in_execution { + OperationHashStatus::Locked => { + return OptionalValue::Some(CURRENT_OPERATION_ALREADY_IN_EXECUTION.into()); + } + OperationHashStatus::NotLocked => { + let last_nonce = self.current_execution_nonce().get(); + if operation_nonce != last_nonce { + sc_panic!(INCORRECT_OPERATION_NONCE); + } + + operation_hash_status_mapper.set(OperationHashStatus::Locked); + self.current_execution_nonce().set(operation_nonce + 1); + } + } + + OptionalValue::None + } +} diff --git a/header-verifier/src/storage.rs b/header-verifier/src/storage.rs new file mode 100644 index 000000000..b7b5c5b2d --- /dev/null +++ b/header-verifier/src/storage.rs @@ -0,0 +1,38 @@ +use structs::{aliases::TxNonce, forge::ContractInfo, OperationHashStatus}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait HeaderVerifierStorageModule { + #[storage_mapper("blsPubKeys")] + fn bls_pub_keys(&self, epoch: u64) -> SetMapper; + + #[storage_mapper_from_address("blsKeysMap")] + fn bls_keys_map( + &self, + sc_address: ManagedAddress, + ) -> MapMapper, ManagedBuffer, ManagedAddress>; + + #[storage_mapper_from_address("setupPhaseComplete")] + fn chain_config_setup_phase_complete( + &self, + sc_address: ManagedAddress, + ) -> SingleValueMapper; + + #[view(operationHashStatus)] + #[storage_mapper("operationHashStatus")] + fn operation_hash_status( + &self, + hash_of_hashes: &ManagedBuffer, + operation_hash: &ManagedBuffer, + ) -> SingleValueMapper; + + #[storage_mapper("hashOfHashesHistory")] + fn hash_of_hashes_history(&self) -> UnorderedSetMapper; + + #[storage_mapper("sovereignContracts")] + fn sovereign_contracts(&self) -> UnorderedSetMapper>; + + #[storage_mapper("operationNonce")] + fn current_execution_nonce(&self) -> SingleValueMapper; +} diff --git a/header-verifier/tests/header_verifier_blackbox_setup.rs b/header-verifier/tests/header_verifier_blackbox_setup.rs index f36bfb246..0822f775c 100644 --- a/header-verifier/tests/header_verifier_blackbox_setup.rs +++ b/header-verifier/tests/header_verifier_blackbox_setup.rs @@ -1,16 +1,21 @@ +use common_test_setup::base_setup::init::{AccountSetup, BaseSetup, ExpectedLogs}; +use common_test_setup::base_setup::log_validations::assert_expected_logs; use common_test_setup::constants::{ - ENSHRINE_ADDRESS, HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS, OWNER_BALANCE, + CHANGE_VALIDATOR_SET_ENDPOINT, ESDT_SAFE_ADDRESS, EXECUTED_BRIDGE_OP_EVENT, + HEADER_VERIFIER_ADDRESS, MVX_ESDT_SAFE_CODE_PATH, OWNER_ADDRESS, OWNER_BALANCE, }; -use common_test_setup::{AccountSetup, BaseSetup}; -use multiversx_sc::{ - api::ManagedTypeApi, - types::{ManagedBuffer, MultiValueEncoded, TestAddress}, +use common_test_setup::log; +use header_verifier::storage::HeaderVerifierStorageModule; +use multiversx_sc::api::ManagedTypeApi; +use multiversx_sc::types::{ + BigUint, ManagedBuffer, MultiValueEncoded, ReturnsHandledOrError, ReturnsResult, + ReturnsResultUnmanaged, TestSCAddress, }; -use multiversx_sc_scenario::{ - api::StaticApi, multiversx_chain_vm::crypto_functions::sha256, ScenarioTxRun, -}; -use multiversx_sc_scenario::{ReturnsHandledOrError, ReturnsLogs}; +use multiversx_sc_scenario::imports::*; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_scenario::DebugApi; use proxies::header_verifier_proxy::HeaderverifierProxy; +use structs::aliases::TxNonce; #[derive(Clone)] pub struct BridgeOperation { @@ -27,58 +32,90 @@ impl HeaderVerifierTestState { #[allow(clippy::new_without_default)] pub fn new() -> Self { let owner_setup = AccountSetup { - address: OWNER_ADDRESS, + address: OWNER_ADDRESS.to_address(), + code_path: None, esdt_balances: None, egld_balance: Some(OWNER_BALANCE.into()), }; - let enshrine_setup = AccountSetup { - address: ENSHRINE_ADDRESS, + let mvx_setup = AccountSetup { + address: ESDT_SAFE_ADDRESS.to_address(), + code_path: Some(MVX_ESDT_SAFE_CODE_PATH), esdt_balances: None, egld_balance: Some(OWNER_BALANCE.into()), }; - let account_setups = vec![owner_setup, enshrine_setup]; + let account_setups = vec![owner_setup, mvx_setup]; let common_setup = BaseSetup::new(account_setups); Self { common_setup } } - pub fn propose_register_esdt_address(&mut self, esdt_address: TestAddress) { - self.common_setup + pub fn register_operations( + &mut self, + signature: &ManagedBuffer, + operation: BridgeOperation, + pub_keys_bitmap: ManagedBuffer, + epoch: u64, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup .world .tx() .from(OWNER_ADDRESS) .to(HEADER_VERIFIER_ADDRESS) .typed(HeaderverifierProxy) - .set_esdt_safe_address(esdt_address) + .register_bridge_operations( + signature, + operation.bridge_operation_hash, + pub_keys_bitmap, + epoch, + operation.operations_hashes, + ) + .returns(ReturnsHandledOrError::new()) .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); } - pub fn propose_register_operations(&mut self, operation: BridgeOperation) { + pub fn with_header_verifier(&mut self, f: F) + where + F: FnOnce(header_verifier::ContractObj), + { self.common_setup .world - .tx() - .from(OWNER_ADDRESS) + .query() .to(HEADER_VERIFIER_ADDRESS) - .typed(HeaderverifierProxy) - .register_bridge_operations( - operation.signature, - operation.bridge_operation_hash, - ManagedBuffer::new(), - ManagedBuffer::new(), - operation.operations_hashes, - ) - .run(); + .whitebox(header_verifier::contract_obj, f); + } + + pub fn last_operation_nonce(&mut self) -> TxNonce { + let mut nonce: TxNonce = 0; + self.with_header_verifier(|sc| { + let current = sc.current_execution_nonce().get(); + nonce = current.saturating_sub(1); + }); + nonce + } + + pub fn next_operation_nonce(&mut self) -> TxNonce { + self.common_setup.next_operation_nonce() } - pub fn propose_remove_executed_hash( + pub fn assert_last_operation_nonce(&mut self, expected: TxNonce) { + let actual = self.last_operation_nonce(); + assert_eq!(actual, expected); + } + + pub fn remove_executed_hash( &mut self, - caller: TestAddress, + caller: TestSCAddress, hash_of_hashes: &ManagedBuffer, operation_hash: &ManagedBuffer, - expected_result: Option<&str>, + expected_error_message: Option<&str>, ) { let response = self .common_setup @@ -88,24 +125,21 @@ impl HeaderVerifierTestState { .to(HEADER_VERIFIER_ADDRESS) .typed(HeaderverifierProxy) .remove_executed_hash(hash_of_hashes, operation_hash) - .returns(ReturnsHandledOrError::new()) - .run(); + .returns(ReturnsResult) + .run() + .into_option(); - match response { - Ok(_) => assert!( - expected_result.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => assert_eq!(expected_result, Some(error.message.as_str())), - }; + self.common_setup + .assert_optional_error_message(response, expected_error_message); } - pub fn propose_lock_operation_hash( + pub fn lock_operation_hash( &mut self, - caller: TestAddress, + caller: TestSCAddress, hash_of_hashes: &ManagedBuffer, operation_hash: &ManagedBuffer, - expected_result: Option<&str>, + operation_nonce: TxNonce, + expected_error_message: Option<&str>, ) { let response = self .common_setup @@ -114,26 +148,25 @@ impl HeaderVerifierTestState { .from(caller) .to(HEADER_VERIFIER_ADDRESS) .typed(HeaderverifierProxy) - .lock_operation_hash(hash_of_hashes, operation_hash) - .returns(ReturnsHandledOrError::new()) - .run(); + .lock_operation_hash(hash_of_hashes, operation_hash, operation_nonce) + .returns(ReturnsResultUnmanaged) + .run() + .into_option(); - match response { - Ok(_) => assert!( - expected_result.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => assert_eq!(expected_result, Some(error.message.as_str())), - }; + self.common_setup + .assert_optional_error_message(response, expected_error_message); } + #[allow(clippy::too_many_arguments)] pub fn change_validator_set( &mut self, signature: &ManagedBuffer, hash_of_hashes: &ManagedBuffer, operation_hash: &ManagedBuffer, - expected_error_message: Option<&str>, - expected_custom_log: Option<&str>, + epoch: u64, + pub_keys_bitmap: &ManagedBuffer, + validator_set: MultiValueEncoded>, + execution_error: Option<&str>, ) { let (logs, response) = self .common_setup @@ -146,20 +179,22 @@ impl HeaderVerifierTestState { signature, hash_of_hashes, operation_hash, - ManagedBuffer::new(), - ManagedBuffer::new(), - MultiValueEncoded::new(), + pub_keys_bitmap, + epoch, + validator_set, ) .returns(ReturnsLogs) .returns(ReturnsHandledOrError::new()) .run(); self.common_setup - .assert_expected_error_message(response, expected_error_message); + .assert_expected_error_message(response, None); - if let Some(custom_log) = expected_custom_log { - self.common_setup.assert_expected_log(logs, custom_log) - }; + let expected_logs = vec![ + log!(CHANGE_VALIDATOR_SET_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: execution_error), + ]; + + assert_expected_logs(logs, expected_logs); } pub fn generate_bridge_operation_struct( @@ -175,7 +210,7 @@ impl HeaderVerifierTestState { bridge_operations.push(operation_hash.clone()); } - let hash_of_hashes = self.get_operation_hash(&appended_hashes); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&appended_hashes.to_vec())); BridgeOperation { signature: ManagedBuffer::new(), @@ -183,21 +218,4 @@ impl HeaderVerifierTestState { operations_hashes: bridge_operations, } } - - pub fn get_operation_hash( - &mut self, - operation: &ManagedBuffer, - ) -> ManagedBuffer { - let mut array = [0; 1024]; - - let len = { - let byte_array = operation.load_to_byte_array(&mut array); - byte_array.len() - }; - - let trimmed_slice = &array[..len]; - let hash = sha256(trimmed_slice); - - ManagedBuffer::from(&hash) - } } diff --git a/header-verifier/tests/header_verifier_blackbox_test.rs b/header-verifier/tests/header_verifier_blackbox_test.rs deleted file mode 100644 index 4b7a273da..000000000 --- a/header-verifier/tests/header_verifier_blackbox_test.rs +++ /dev/null @@ -1,304 +0,0 @@ -use common_test_setup::constants::{ENSHRINE_ADDRESS, HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS}; -use error_messages::{ - CURRENT_OPERATION_NOT_REGISTERED, NO_ESDT_SAFE_ADDRESS, ONLY_ESDT_SAFE_CALLER, - OUTGOING_TX_HASH_ALREADY_REGISTERED, -}; -use header_verifier::{Headerverifier, OperationHashStatus}; -use header_verifier_blackbox_setup::*; -use multiversx_sc::types::ManagedBuffer; -use multiversx_sc_scenario::{DebugApi, ScenarioTxWhitebox}; - -mod header_verifier_blackbox_setup; - -#[test] -fn test_deploy() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); -} - -#[test] -fn test_register_esdt_address() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - - state - .common_setup - .world - .query() - .to(HEADER_VERIFIER_ADDRESS) - .whitebox(header_verifier::contract_obj, |sc| { - let esdt_address = sc.esdt_safe_address().get(); - - assert_eq!(esdt_address, ENSHRINE_ADDRESS); - }) -} - -#[test] -fn test_register_bridge_operation() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - - state - .common_setup - .world - .query() - .to(HEADER_VERIFIER_ADDRESS) - .whitebox(header_verifier::contract_obj, |sc| { - let hash_of_hashes: ManagedBuffer = - ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); - - assert!(!sc.hash_of_hashes_history().is_empty()); - assert!(sc.hash_of_hashes_history().len() == 1); - assert!(sc.hash_of_hashes_history().contains(&hash_of_hashes)); - - for operation_hash in operation.operations_hashes { - let operation_hash_debug_api = ManagedBuffer::from(operation_hash.to_vec()); - - let pending_hashes_mapper = - sc.operation_hash_status(&hash_of_hashes, &operation_hash_debug_api); - - let is_mapper_empty = pending_hashes_mapper.is_empty(); - let is_operation_hash_locked = pending_hashes_mapper.get(); - - assert!(!is_mapper_empty); - assert!(is_operation_hash_locked == OperationHashStatus::NotLocked); - } - }); -} - -#[test] -fn test_remove_executed_hash_caller_not_esdt_address() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - state.propose_remove_executed_hash( - OWNER_ADDRESS, - &operation.bridge_operation_hash, - &operation_1, - Some(ONLY_ESDT_SAFE_CALLER), - ); -} - -#[test] -fn test_remove_executed_hash_no_esdt_address_registered() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - state.propose_remove_executed_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_1, - Some(NO_ESDT_SAFE_ADDRESS), - ); -} - -#[test] -fn test_remove_one_executed_hash() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_hash_1 = ManagedBuffer::from("operation_1"); - let operation_hash_2 = ManagedBuffer::from("operation_2"); - let operation = - state.generate_bridge_operation_struct(vec![&operation_hash_1, &operation_hash_2]); - - state.propose_register_operations(operation.clone()); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - - state.propose_remove_executed_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_hash_1, - None, - ); - - state - .common_setup - .world - .query() - .to(HEADER_VERIFIER_ADDRESS) - .whitebox(header_verifier::contract_obj, |sc| { - let hash_of_hashes: ManagedBuffer = - ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); - let operation_hash_debug_api = ManagedBuffer::from(operation_hash_2.to_vec()); - - let pending_hashes_mapper = - sc.operation_hash_status(&hash_of_hashes, &operation_hash_debug_api); - - let is_hash_locked = pending_hashes_mapper.get(); - let is_mapper_empty = pending_hashes_mapper.is_empty(); - - assert!(!is_mapper_empty); - assert!(is_hash_locked == OperationHashStatus::NotLocked); - }); -} - -#[test] -fn test_remove_all_executed_hashes() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - - state.propose_remove_executed_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_1, - None, - ); - - state.propose_remove_executed_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_2, - None, - ); - state - .common_setup - .world - .query() - .to(HEADER_VERIFIER_ADDRESS) - .whitebox(header_verifier::contract_obj, |sc| { - let hash_of_hashes: ManagedBuffer = - ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); - let operation_hash_debug_api_1 = ManagedBuffer::from(operation_1.to_vec()); - let operation_hash_debug_api_2 = ManagedBuffer::from(operation_2.to_vec()); - assert!(sc - .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_1) - .is_empty()); - assert!(sc - .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_2) - .is_empty()); - assert!(sc.hash_of_hashes_history().contains(&hash_of_hashes)); - }); -} - -#[test] -fn test_lock_operation_not_registered() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_lock_operation_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_1, - Some(CURRENT_OPERATION_NOT_REGISTERED), - ); -} - -#[test] -fn test_lock_operation() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - state.propose_register_esdt_address(ENSHRINE_ADDRESS); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - - state.propose_lock_operation_hash( - ENSHRINE_ADDRESS, - &operation.bridge_operation_hash, - &operation_1, - None, - ); - - state - .common_setup - .world - .query() - .to(HEADER_VERIFIER_ADDRESS) - .whitebox(header_verifier::contract_obj, |sc| { - let hash_of_hashes: ManagedBuffer = - ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); - let operation_hash_debug_api_1 = ManagedBuffer::from(operation_1.to_vec()); - let operation_hash_debug_api_2 = ManagedBuffer::from(operation_2.to_vec()); - let is_hash_1_locked = sc - .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_1) - .get(); - let is_hash_2_locked = sc - .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_2) - .get(); - - assert!(is_hash_1_locked == OperationHashStatus::Locked); - assert!(is_hash_2_locked == OperationHashStatus::NotLocked); - }) -} - -#[test] -fn test_change_validator_set() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_hash = ManagedBuffer::from("operation_1"); - let hash_of_hashes = state.get_operation_hash(&operation_hash); - - state.change_validator_set( - &ManagedBuffer::new(), - &hash_of_hashes, - &operation_hash, - None, - Some("executedBridgeOp"), - ); -} - -#[test] -fn test_change_validator_set_operation_already_registered() { - let mut state = HeaderVerifierTestState::new(); - - state.common_setup.deploy_header_verifier(); - - let operation_1 = ManagedBuffer::from("operation_1"); - let operation_2 = ManagedBuffer::from("operation_2"); - let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); - - state.propose_register_operations(operation.clone()); - - state.change_validator_set( - &ManagedBuffer::new(), - &operation.bridge_operation_hash, - &operation.operations_hashes.to_vec().get(0), - Some(OUTGOING_TX_HASH_ALREADY_REGISTERED), - None, - ); -} diff --git a/header-verifier/tests/header_verifier_blackbox_tests.rs b/header-verifier/tests/header_verifier_blackbox_tests.rs new file mode 100644 index 000000000..ce9cfa6b5 --- /dev/null +++ b/header-verifier/tests/header_verifier_blackbox_tests.rs @@ -0,0 +1,1175 @@ +use common_test_setup::base_setup::helpers::BLSKey; +use common_test_setup::constants::{ + CHAIN_CONFIG_ADDRESS, ESDT_SAFE_ADDRESS, HEADER_VERIFIER_ADDRESS, OWNER_ADDRESS, +}; +use error_messages::{ + BLS_KEY_NOT_REGISTERED, CALLER_NOT_FROM_CURRENT_SOVEREIGN, + CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE, CURRENT_OPERATION_ALREADY_IN_EXECUTION, + CURRENT_OPERATION_NOT_REGISTERED, INCORRECT_OPERATION_NONCE, INVALID_EPOCH, + NO_VALIDATORS_FOR_GIVEN_EPOCH, NO_VALIDATORS_FOR_PREVIOUS_EPOCH, + OUTGOING_TX_HASH_ALREADY_REGISTERED, SETUP_PHASE_NOT_COMPLETED, +}; +use header_verifier::header_utils::HeaderVerifierUtilsModule; +use header_verifier::storage::HeaderVerifierStorageModule; +use header_verifier_blackbox_setup::*; +use multiversx_sc::imports::{BigUint, ManagedVec, StorageClearable}; +use multiversx_sc::types::ReturnsHandledOrError; +use multiversx_sc::{ + imports::OptionalValue, + types::{ManagedBuffer, MultiEgldOrEsdtPayment, MultiValueEncoded}, +}; +use multiversx_sc_scenario::api::StaticApi; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_scenario::{DebugApi, ScenarioTxRun, ScenarioTxWhitebox}; +use proxies::header_verifier_proxy::HeaderverifierProxy; +use structs::configs::SovereignConfig; +use structs::OperationHashStatus; +use structs::{forge::ScArray, ValidatorData}; + +mod header_verifier_blackbox_setup; + +#[test] +fn test_deploy() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_header_verifier(vec![]); +} + +/// ### TEST +/// H-VERIFIER_REGISTER_OPERATION_FAIL +/// +/// ### ACTION +/// Call 'register_operations' with valid operations +/// +/// ### EXPECTED +/// Error SETUP_PHASE_NOT_COMPLETED +#[test] +fn register_bridge_operation_setup_not_completed() { + let mut state = HeaderVerifierTestState::new(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + state.register_operations( + &operation.signature, + operation.clone(), + bitmap, + 0, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// H-VERIFIER_REGISTER_OPERATION_NO_VALIDATORS +/// +/// ### ACTION +/// Call 'register_operations' without registering validators for the given epoch +/// +/// ### EXPECTED +/// Error NO_VALIDATORS_FOR_GIVEN_EPOCH +#[test] +fn test_register_bridge_operation_no_validators_for_epoch() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &MultiEgldOrEsdtPayment::new(), None); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, _pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations( + &signature, + operation, + bitmap, + 1, + Some(NO_VALIDATORS_FOR_GIVEN_EPOCH), + ); +} + +/// ### TEST +/// H-VERIFIER_REGISTER_OPERATION_OK +/// +/// ### ACTION +/// Call 'register_operations' with valid operations and setup completed +/// +/// ### EXPECTED +/// The operations are registered in the contract storage +#[test] +fn test_register_bridge_operation() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap.clone(), 0, None); + + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let hash_of_hashes: ManagedBuffer = + ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); + + assert!(!sc.hash_of_hashes_history().is_empty()); + assert!(sc.hash_of_hashes_history().len() == 1); + assert!(sc.hash_of_hashes_history().contains(&hash_of_hashes)); + + for operation_hash in operation.operations_hashes { + let operation_hash_debug_api = ManagedBuffer::from(operation_hash.to_vec()); + + let pending_hashes_mapper = + sc.operation_hash_status(&hash_of_hashes, &operation_hash_debug_api); + + let is_mapper_empty = pending_hashes_mapper.is_empty(); + let is_operation_hash_locked = pending_hashes_mapper.get(); + + assert!(!is_mapper_empty); + assert!(is_operation_hash_locked == OperationHashStatus::NotLocked); + } + }); +} + +/// ### TEST +/// H-VERIFIER_REMOVE_HASH_FAIL +/// +/// ### ACTION +/// Call 'remove_executed_hash()' without registering any esdt safe address +/// +/// ### EXPECTED +/// Error: CALLER_NOT_FROM_CURRENT_SOVEREIGN +#[test] +fn test_remove_executed_hash_no_esdt_address_registered() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + state.remove_executed_hash( + ESDT_SAFE_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN), + ); +} + +/// ### TEST +/// H-VERIFIER_REMOVE_HASH_OK +/// +/// ### ACTION +/// Call 'remove_executed_hash()' after registering the esdt safe address +/// +/// ### EXPECTED +/// The operation hash is removed from the contract storage +#[test] +fn test_remove_one_executed_hash() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash_1 = ManagedBuffer::from("operation_1"); + let operation_hash_2 = ManagedBuffer::from("operation_2"); + let operation = + state.generate_bridge_operation_struct(vec![&operation_hash_1, &operation_hash_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + state.remove_executed_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_hash_1, + None, + ); + + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let hash_of_hashes: ManagedBuffer = + ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); + let operation_hash_debug_api = ManagedBuffer::from(operation_hash_2.to_vec()); + + let pending_hashes_mapper = + sc.operation_hash_status(&hash_of_hashes, &operation_hash_debug_api); + + let is_hash_locked = pending_hashes_mapper.get(); + let is_mapper_empty = pending_hashes_mapper.is_empty(); + + assert!(!is_mapper_empty); + assert!(is_hash_locked == OperationHashStatus::NotLocked); + }); +} + +/// ### TEST +/// H-VERIFIER_REMOVE_HASH_OK +/// +/// ### ACTION +/// Call 'remove_executed_hash()' after registering the esdt safe address +/// +/// ### EXPECTED +/// All the operation hashes are removed from the contract storage +#[test] +fn test_remove_all_executed_hashes() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + + state.remove_executed_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + None, + ); + + state.remove_executed_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_2, + None, + ); + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let hash_of_hashes: ManagedBuffer = + ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); + let operation_hash_debug_api_1 = ManagedBuffer::from(operation_1.to_vec()); + let operation_hash_debug_api_2 = ManagedBuffer::from(operation_2.to_vec()); + assert!(sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_1) + .is_empty()); + assert!(sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_2) + .is_empty()); + assert!(sc.hash_of_hashes_history().contains(&hash_of_hashes)); + }); +} + +/// ### TEST +/// H-VERIFIER_LOCK_OPERATION_FAIL +/// +/// ### ACTION +/// Call 'lock_operation_hash()' without registering the operation +/// +/// ### EXPECTED +/// Error: CURRENT_OPERATION_NOT_REGISTERED +#[test] +fn test_lock_operation_not_registered() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + + state.lock_operation_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + 1, + Some(CURRENT_OPERATION_NOT_REGISTERED), + ); +} + +/// ### TEST +/// H-VERIFIER_LOCK_OPERATION_FAIL +/// +/// ### ACTION +/// Call 'lock_operation_hash()' from an unregistered sc +/// +/// ### EXPECTED +/// Error: CALLER_NOT_FROM_CURRENT_SOVEREIGN +#[test] +fn test_lock_operation_caller_not_from_sovereign() { + let mut state = HeaderVerifierTestState::new(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + + state.lock_operation_hash( + ESDT_SAFE_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + 0, + Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN), + ); +} + +/// ### TEST +/// H-VERIFIER_LOCK_OPERATION_OK +/// +/// ### ACTION +/// Call 'lock_operation_hash()' after registering the operations +/// +/// ### EXPECTED +/// Only the first operation hash is locked in the contract storage +#[test] +fn test_lock_operation() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + + state.assert_last_operation_nonce(0); + + let expected_operation_nonce = state.next_operation_nonce(); + + state.lock_operation_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + expected_operation_nonce, + None, + ); + + state.assert_last_operation_nonce(expected_operation_nonce); + + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let hash_of_hashes: ManagedBuffer = + ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); + let operation_hash_debug_api_1 = ManagedBuffer::from(operation_1.to_vec()); + let operation_hash_debug_api_2 = ManagedBuffer::from(operation_2.to_vec()); + let is_hash_1_locked = sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_1) + .get(); + let is_hash_2_locked = sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_2) + .get(); + + assert!(is_hash_1_locked == OperationHashStatus::Locked); + assert!(is_hash_2_locked == OperationHashStatus::NotLocked); + }) +} + +/// ### TEST +/// H-VERIFIER_LOCK_OPERATION_FAIL +/// +/// ### ACTION +/// Call 'lock_operation_hash()' with a stale operation nonce value +/// +/// ### EXPECTED +/// Error: INCORRECT_OPERATION_NONCE +#[test] +fn test_lock_operation_incorrect_nonce_rejected() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash_1 = ManagedBuffer::from("operation_nonce_fail_1"); + let operation_hash_2 = ManagedBuffer::from("operation_nonce_fail_2"); + let operation = + state.generate_bridge_operation_struct(vec![&operation_hash_1, &operation_hash_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + + state.assert_last_operation_nonce(0); + let expected_next_nonce = state.next_operation_nonce(); + let incorrect_nonce = expected_next_nonce.checked_add(1).unwrap(); + + assert_eq!( + state + .common_setup + .world + .tx() + .from(CHAIN_CONFIG_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .typed(HeaderverifierProxy) + .lock_operation_hash( + operation.bridge_operation_hash, + operation_hash_1, + incorrect_nonce, + ) + .returns(ReturnsHandledOrError::new()) + .run() + .err() + .unwrap() + .message, + INCORRECT_OPERATION_NONCE + ); + + state.assert_last_operation_nonce(expected_next_nonce); +} + +/// ### TEST +/// H-VERIFIER_LOCK_OPERATION_FAIL +/// +/// ### ACTION +/// Call 'lock_operation_hash()' on already locked hash +/// +/// ### EXPECTED +/// Error CURRENT_OPERATION_ALREADY_IN_EXECUTION +#[test] +fn test_lock_operation_hash_already_locked() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_1 = ManagedBuffer::from("operation_1"); + let operation_2 = ManagedBuffer::from("operation_2"); + let operation = state.generate_bridge_operation_struct(vec![&operation_1, &operation_2]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.register_operations(&signature, operation.clone(), bitmap, 0, None); + + state.assert_last_operation_nonce(0); + + let expected_operation_nonce = state.next_operation_nonce(); + + state.lock_operation_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + expected_operation_nonce, + None, + ); + + state.assert_last_operation_nonce(expected_operation_nonce); + + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let hash_of_hashes: ManagedBuffer = + ManagedBuffer::from(operation.bridge_operation_hash.to_vec()); + let operation_hash_debug_api_1 = ManagedBuffer::from(operation_1.to_vec()); + let operation_hash_debug_api_2 = ManagedBuffer::from(operation_2.to_vec()); + let is_hash_1_locked = sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_1) + .get(); + let is_hash_2_locked = sc + .operation_hash_status(&hash_of_hashes, &operation_hash_debug_api_2) + .get(); + + assert!(is_hash_1_locked == OperationHashStatus::Locked); + assert!(is_hash_2_locked == OperationHashStatus::NotLocked); + }); + + let next_operation_nonce = state.next_operation_nonce(); + + state.lock_operation_hash( + CHAIN_CONFIG_ADDRESS, + &operation.bridge_operation_hash, + &operation_1, + next_operation_nonce, + Some(CURRENT_OPERATION_ALREADY_IN_EXECUTION), + ); + + state.assert_last_operation_nonce(expected_operation_nonce); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_OK +/// +/// ### ACTION +/// Call 'change_validators_set()' with a valid operation hash +/// +/// ### EXPECTED +/// The validator set is changed in the contract storage +#[test] +fn test_change_validator_set() { + let mut state = HeaderVerifierTestState::new(); + let sovereign_config = SovereignConfig { + max_validators: 3, + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(sovereign_config), None); + + let mut registered_bls_keys: ManagedVec> = + ManagedVec::new(); + let operation_hash = ManagedBuffer::from("operation_1"); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, pub_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + let genesis_validator = pub_keys[0].clone(); + registered_bls_keys.push(genesis_validator.clone()); + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + for id in 2..4 { + let validator_bls_key = BLSKey::random(); + registered_bls_keys.push(validator_bls_key.clone()); + let validator_data = ValidatorData { + id: BigUint::from(id as u32), + address: OWNER_ADDRESS.to_managed_address(), + bls_key: validator_bls_key, + }; + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + state.common_setup.register_validator_operation( + validator_data, + signature.clone(), + bitmap.clone(), + epoch, + ); + } + + let mut validator_set = MultiValueEncoded::new(); + validator_set.push(BigUint::from(1u32)); + validator_set.push(BigUint::from(2u32)); + validator_set.push(BigUint::from(3u32)); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch_for_new_set = 1; + + let (change_validator_set_sig, change_validator_set_pub_keys) = + state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let pub_key = ManagedBuffer::new_from_bytes(&change_validator_set_pub_keys[0].to_vec()); + sc.bls_pub_keys(0).clear(); + sc.bls_pub_keys(0).insert(pub_key); + }); + + state.change_validator_set( + &change_validator_set_sig, + &hash_of_hashes, + &operation_hash, + epoch_for_new_set, + &bitmap, + validator_set, + None, + ); + + state + .common_setup + .check_bls_key_for_epoch_in_header_verifier(epoch_for_new_set, ®istered_bls_keys); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_FAIL +/// +/// ### ACTION +/// Call 'change_validator_set()' for the genesis epoch +/// +/// ### EXPECTED +/// Error INVALID_EPOCH is emitted +#[test] +fn test_change_validator_invalid_epoch() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let genesis_validator = BLSKey::random(); + state + .common_setup + .register(&genesis_validator, &MultiEgldOrEsdtPayment::default(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let operation_hash = ManagedBuffer::from("operation_1"); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let (signature, _) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + let bitmap = state.common_setup.full_bitmap(1); + let validator_set = MultiValueEncoded::new(); + let epoch = 0u64; + + state.change_validator_set( + &signature, + &hash_of_hashes, + &operation_hash, + epoch, + &bitmap, + validator_set, + Some(INVALID_EPOCH), + ); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_FAIL +/// +/// ### ACTION +/// Call 'change_validator_set()' when the previous epoch has no registered validators +/// +/// ### EXPECTED +/// Error NO_VALIDATORS_FOR_PREVIOUS_EPOCH is emitted +#[test] +fn change_validator_set_previous_epoch_has_no_validators() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let genesis_validator = BLSKey::random(); + state + .common_setup + .register(&genesis_validator, &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let operation_hash = ManagedBuffer::from("operation_1"); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let signature = ManagedBuffer::new(); + let bitmap = ManagedBuffer::new(); + let validator_set = MultiValueEncoded::new(); + let epoch = 2u64; + + state.change_validator_set( + &signature, + &hash_of_hashes, + &operation_hash, + epoch, + &bitmap, + validator_set, + Some(NO_VALIDATORS_FOR_PREVIOUS_EPOCH), + ); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_FAIL +/// +/// ### ACTION +/// Call 'change_validator_set()' before registering the operation +/// +/// ### EXPECTED +/// Error OUTGOING_TX_HASH_ALREADY_REGISTERED +#[test] +fn test_change_validator_set_operation_already_registered() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash_1 = ManagedBuffer::from("operation_1"); + let operation = state.generate_bridge_operation_struct(vec![&operation_hash_1]); + let bitmap = state.common_setup.full_bitmap(1); + + let (signature, pub_keys) = state + .common_setup + .get_sig_and_pub_keys(1, &operation.bridge_operation_hash); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.change_validator_set( + &signature, + &operation.bridge_operation_hash, + &operation_hash_1, + 1, + &bitmap, + MultiValueEncoded::new(), + None, + ); + + state.change_validator_set( + &signature, + &operation.bridge_operation_hash, + &operation_hash_1, + 1, + &bitmap, + MultiValueEncoded::new(), + Some(OUTGOING_TX_HASH_ALREADY_REGISTERED), + ); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_FAIL_BLS_KEY +/// +/// ### ACTION +/// Call 'change_validator_set()' with a validator id that is not registered +/// +/// ### EXPECTED +/// Error BLS_KEY_NOT_REGISTERED is emitted +#[test] +fn test_change_validator_set_bls_key_not_found() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash = ManagedBuffer::from("operation_missing_validator"); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, pub_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state + .common_setup + .register(&pub_keys[0], &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 1u64; + + let mut validator_set = MultiValueEncoded::new(); + validator_set.push(BigUint::from(999u32)); + + state.change_validator_set( + &signature, + &hash_of_hashes, + &operation_hash, + epoch, + &bitmap, + validator_set, + Some(BLS_KEY_NOT_REGISTERED), + ); +} + +/// ### TEST +/// H-VERIFIER_CHANGE_VALIDATORS_OK +/// +/// ### ACTION +/// Call 'change_validators_set()' for four epochs +/// +/// ### EXPECTED +/// The validator set is changed in the contract storage and the genesis epoch is cleared +#[ignore = "Ignore until workaround is found"] +#[test] +fn test_change_multiple_validator_sets() { + let mut state = HeaderVerifierTestState::new(); + let sovereign_config = SovereignConfig { + max_validators: 11, + ..SovereignConfig::default_config_for_test() + }; + + state + .common_setup + .deploy_chain_config(OptionalValue::Some(sovereign_config), None); + + state + .common_setup + .register(&BLSKey::random(), &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let mut last_bls_key_id = 1u32; + for epoch in 1..10 { + let validator_bls_key = BLSKey::random(); + last_bls_key_id += 1; + let validator_data = ValidatorData { + id: BigUint::from(last_bls_key_id), + address: OWNER_ADDRESS.to_managed_address(), + bls_key: validator_bls_key.clone(), + }; + + let signature = ManagedBuffer::new(); + + let bitmap = state.common_setup.full_bitmap(1); + + state.common_setup.register_validator_operation( + validator_data, + signature.clone(), + bitmap.clone(), + epoch - 1, + ); + + let operation_hash = ManagedBuffer::from(format!("validators_epoch_{}", epoch)); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let mut validator_set = MultiValueEncoded::new(); + validator_set.push(BigUint::from(epoch + 1)); + + state.change_validator_set( + &ManagedBuffer::new(), + &hash_of_hashes, + &operation_hash, + epoch, + &bitmap, + validator_set, + None, + ); + + let mut bls_keys: ManagedVec> = ManagedVec::new(); + bls_keys.push(validator_bls_key); + state + .common_setup + .check_bls_key_for_epoch_in_header_verifier(epoch, &bls_keys); + + if epoch >= 3 { + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + assert!(sc.bls_pub_keys(epoch - 3).is_empty()); + }) + } + } +} + +/// ### TEST +/// H-VERIFIER_COMPLETE_SETUP_PHASE +/// +/// ### ACTION +/// Call 'complete_setup_phase()' without chain config setup completed +/// +/// ### EXPECTED +/// Error: CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE +#[test] +fn test_complete_setup_phase_chain_config_fail() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + state + .common_setup + .complete_header_verifier_setup_phase(Some(CHAIN_CONFIG_SETUP_PHASE_NOT_COMPLETE)); +} + +#[test] +fn test_get_approving_validators() { + let mut state = HeaderVerifierTestState::new(); + + state.common_setup.deploy_header_verifier(vec![]); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + // Create hardcoded BLS keys for testing + let validator0_bls_key = BLSKey::random(); // genesis validator + let validator1_bls_key = BLSKey::random(); + let validator2_bls_key = BLSKey::random(); + let validator3_bls_key = BLSKey::random(); + let validator4_bls_key = BLSKey::random(); + + let epoch = 0u64; + + // Store BLS keys in the contract + sc.bls_pub_keys(epoch).insert(validator0_bls_key.clone()); + sc.bls_pub_keys(epoch).insert(validator1_bls_key.clone()); + sc.bls_pub_keys(epoch).insert(validator2_bls_key.clone()); + sc.bls_pub_keys(epoch).insert(validator3_bls_key.clone()); + sc.bls_pub_keys(epoch).insert(validator4_bls_key.clone()); + + // Test Case 1: Bitmap [0b00000001] - Only validator at index 0 approves + let bitmap = ManagedBuffer::new_from_bytes(&[0b00000001]); + let approving_validators = sc.get_approving_validators(epoch, &bitmap, 5); + assert_eq!(approving_validators.len(), 1); + assert_eq!(approving_validators.get(0).clone(), validator0_bls_key); + + // Test Case 2: Bitmap [0b00000101] - Validators at indices 0 and 2 approve + let bitmap = ManagedBuffer::new_from_bytes(&[0b00000101]); + let approving_validators = sc.get_approving_validators(epoch, &bitmap, 5); + assert_eq!(approving_validators.len(), 2); + assert_eq!(approving_validators.get(0).clone(), validator0_bls_key); + assert_eq!(approving_validators.get(1).clone(), validator2_bls_key); + + // Test Case 3: Bitmap [0b11111111] - All validators approve + let bitmap = ManagedBuffer::new_from_bytes(&[0b11111111]); + let approving_validators = sc.get_approving_validators(epoch, &bitmap, 5); + assert_eq!(approving_validators.len(), 5); + assert_eq!(approving_validators.get(0).clone(), validator0_bls_key); + assert_eq!(approving_validators.get(1).clone(), validator1_bls_key); + assert_eq!(approving_validators.get(2).clone(), validator2_bls_key); + assert_eq!(approving_validators.get(3).clone(), validator3_bls_key); + assert_eq!(approving_validators.get(4).clone(), validator4_bls_key); + + // Test Case 4: Bitmap [0b00000000] - No validators approve + let bitmap = ManagedBuffer::new_from_bytes(&[0b00000000]); + let approving_validators = sc.get_approving_validators(epoch, &bitmap, 5); + assert_eq!(approving_validators.len(), 0); + + // Test Case 5: Bitmap [0b00001010] - Validators at indices 1 and 3 approve + let bitmap = ManagedBuffer::new_from_bytes(&[0b00001010]); + let approving_validators = sc.get_approving_validators(epoch, &bitmap, 5); + assert_eq!(approving_validators.len(), 2); + assert_eq!(approving_validators.get(0).clone(), validator1_bls_key); + assert_eq!(approving_validators.get(1).clone(), validator3_bls_key); + }); +} diff --git a/header-verifier/wasm-header-verifier-full/Cargo.lock b/header-verifier/wasm-header-verifier-full/Cargo.lock deleted file mode 100644 index b428aa42b..000000000 --- a/header-verifier/wasm-header-verifier-full/Cargo.lock +++ /dev/null @@ -1,269 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "cross-chain" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "header-verifier" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "multiversx-sc", - "proxies", - "structs", -] - -[[package]] -name = "header-verifier-full-wasm" -version = "0.0.0" -dependencies = [ - "header-verifier", - "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 = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-modules" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/header-verifier/wasm-header-verifier-full/Cargo.toml b/header-verifier/wasm-header-verifier-full/Cargo.toml deleted file mode 100644 index 84971cac6..000000000 --- a/header-verifier/wasm-header-verifier-full/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "header-verifier-full-wasm" -version = "0.0.0" -edition = "2021" -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.header-verifier] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" - -[workspace] -members = ["."] diff --git a/header-verifier/wasm-header-verifier-full/src/lib.rs b/header-verifier/wasm-header-verifier-full/src/lib.rs deleted file mode 100644 index a3d86b7fa..000000000 --- a/header-verifier/wasm-header-verifier-full/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Upgrade: 1 -// Endpoints: 6 -// Async Callback (empty): 1 -// Total number of exported functions: 9 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - header_verifier - ( - init => init - upgrade => upgrade - registerBlsPubKeys => register_bls_pub_keys - registerBridgeOps => register_bridge_operations - changeValidatorSet => change_validator_set - setEsdtSafeAddress => set_esdt_safe_address - removeExecutedHash => remove_executed_hash - lockOperationHash => lock_operation_hash - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/header-verifier/wasm-header-verifier-view/Cargo.toml b/header-verifier/wasm-header-verifier-view/Cargo.toml deleted file mode 100644 index d58491091..000000000 --- a/header-verifier/wasm-header-verifier-view/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "header-verifier-view-wasm" -version = "0.0.0" -edition = "2021" -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.header-verifier] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" - -[workspace] -members = ["."] diff --git a/header-verifier/wasm-header-verifier-view/src/lib.rs b/header-verifier/wasm-header-verifier-view/src/lib.rs deleted file mode 100644 index f06c74c1e..000000000 --- a/header-verifier/wasm-header-verifier-view/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 0 -// Async Callback (empty): 1 -// Total number of exported functions: 2 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::external_view_init! {} - -multiversx_sc_wasm_adapter::external_view_endpoints! { - header_verifier - ( - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/header-verifier/wasm-header-verifier/Cargo.lock b/header-verifier/wasm-header-verifier/Cargo.lock deleted file mode 100644 index 88da5e6dd..000000000 --- a/header-verifier/wasm-header-verifier/Cargo.lock +++ /dev/null @@ -1,269 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "cross-chain" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "header-verifier" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "multiversx-sc", - "proxies", - "structs", -] - -[[package]] -name = "header-verifier-wasm" -version = "0.0.0" -dependencies = [ - "header-verifier", - "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 = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-modules" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe/Cargo.lock b/header-verifier/wasm/Cargo.lock similarity index 72% rename from sov-esdt-safe/wasm-sov-esdt-safe/Cargo.lock rename to header-verifier/wasm/Cargo.lock index 8a1f2616a..19f08cb0c 100644 --- a/sov-esdt-safe/wasm-sov-esdt-safe/Cargo.lock +++ b/header-verifier/wasm/Cargo.lock @@ -10,56 +10,88 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", +] [[package]] name = "cross-chain" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "fee-market" +name = "generic-array" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" +dependencies = [ + "typenum", +] + +[[package]] +name = "header-verifier" version = "0.1.0" dependencies = [ + "common-utils", + "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "proxies", + "setup-phase", "structs", - "utils", ] [[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" +name = "header-verifier-wasm" +version = "0.0.0" dependencies = [ - "typenum", + "header-verifier", + "multiversx-sc-wasm-adapter", ] [[package]] @@ -70,15 +102,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -86,9 +118,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -102,9 +134,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -114,9 +146,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -126,9 +158,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -139,18 +171,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -175,9 +207,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -192,18 +224,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -213,39 +245,17 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "sov-esdt-safe" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "fee-market", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "setup-phase", - "structs", - "testing-sc", - "utils", -] - -[[package]] -name = "sov-esdt-safe-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "sov-esdt-safe", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "structs" @@ -256,22 +266,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "testing-sc" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - [[package]] name = "typenum" version = "1.18.0" @@ -280,21 +283,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/header-verifier/wasm-header-verifier/Cargo.toml b/header-verifier/wasm/Cargo.toml similarity index 96% rename from header-verifier/wasm-header-verifier/Cargo.toml rename to header-verifier/wasm/Cargo.toml index 1a1247671..63839ee1d 100644 --- a/header-verifier/wasm-header-verifier/Cargo.toml +++ b/header-verifier/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/header-verifier/wasm-header-verifier/src/lib.rs b/header-verifier/wasm/src/lib.rs similarity index 89% rename from header-verifier/wasm-header-verifier/src/lib.rs rename to header-verifier/wasm/src/lib.rs index a3d86b7fa..e65db7957 100644 --- a/header-verifier/wasm-header-verifier/src/lib.rs +++ b/header-verifier/wasm/src/lib.rs @@ -20,10 +20,10 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - registerBlsPubKeys => register_bls_pub_keys + completeSetupPhase => complete_setup_phase + operationHashStatus => operation_hash_status registerBridgeOps => register_bridge_operations changeValidatorSet => change_validator_set - setEsdtSafeAddress => set_esdt_safe_address removeExecutedHash => remove_executed_hash lockOperationHash => lock_operation_hash ) diff --git a/interactor/Cargo.toml b/interactor/Cargo.toml index 46b32e9da..26ecde8c6 100644 --- a/interactor/Cargo.toml +++ b/interactor/Cargo.toml @@ -1,7 +1,3 @@ -[[bin]] -name = "rust-interact" -path = "src/interactor_main.rs" - [package] name = "rust-interact" version = "0.1.0" @@ -13,12 +9,7 @@ publish = false path = "src/interact.rs" [dependencies] -toml = "0.8.6" - -[dependencies.base64] -version = "0.21.0" -default-features = false -features = ["alloc"] +toml = "0.8" [dependencies.mvx-esdt-safe] path = "../mvx-esdt-safe" @@ -36,10 +27,10 @@ path = "../common/proxies" path = "../common/structs" [dependencies.multiversx-sc-snippets] -version = "0.57.1" +version = "0.63.0" [dependencies.multiversx-sc] -version = "0.57.1" +version = "0.63.0" [dependencies.clap] version = "4.4.7" @@ -58,5 +49,18 @@ path = "../common/error-messages" [dependencies.common-test-setup] path = "../common/common-test-setup" +[dependencies.common-interactor] +path = "../common/common-interactor" + +[dependencies.cross-chain] +path = "../common/cross-chain" + +[dev-dependencies] +rstest = "0.25.0" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.63.0" +features = ["bls"] + [features] chain-simulator-tests = [] diff --git a/interactor/HowToRun.md b/interactor/HowToRun.md new file mode 100644 index 000000000..6c364ffc3 --- /dev/null +++ b/interactor/HowToRun.md @@ -0,0 +1,81 @@ +# Chain Simulator Interactor Workflow + +This project uses the **Chain Simulator** to run tests against a **shared deployment** (“common state”) while ensuring each test also runs with its own **clean, per-test state**. + +--- + +## Quick Start + +1. **Start the Chain Simulator** + + ```bash + sc-meta cs start + ``` + + > Keep this running while you execute tests. + +2. **If you restart the simulator**, delete `state.toml` before running any test: + + ```bash + # Linux / macOS + find . -name state.toml -delete + + # Or manually: + rm -f path/to/interactor/state.toml + + ``` + + For a more convenient way to do the first 2 steps, a terminal alias can be set that **starts the chain simulator** and also **deletes the state.toml**. Example: + ``` + cs() { + rm -f interactor/state.toml + command sc-meta cs start "$@" + } + ``` + +3. **Run the deployment test** + + Run `deploy_setup` inside `always_deploy_setup_first.rs`. This creates the **common state**. + + Examples: + + ```bash + cargo test --package rust-interact --test always_deploy_setup_first --all-features -- deploy_setup --exact --show-output + ``` + +4. **Run any tests you want** + + With the common state in place, you can run specific tests or whole suites: + + ```bash + # Single test + cargo test --package rust-interact --test 'file_name_without_rs' --all-features -- 'test_name' --show-output + + # All tests in a file + cargo test --package rust-interact --test 'file_name_without_rs' --all-features -- --show-output + ``` + +--- + +## How It Works + +- The **deployment test** (`always_deploy_setup_first.rs::test`) seeds the simulator with a **common state** (contracts deployed once). +- **Subsequent tests reuse this state**, avoiding multiple redeploys. +- Each test still runs in **isolation**: + - A **per-test state** is created and deleted at the beginning of each new test. + - The **common state remains** intact across all tests. + +This makes tests **faster** and ensures **consistent deployments**. + +--- + +## Troubleshooting + +- **“Missing address / not deployed” error** + → Re-run the deployment test (`always_deploy_setup_first.rs::test`). + +- **State seems stale or inconsistent** + → Stop the simulator, delete `state.toml`, restart it, run the deployment test, then your tests. + +- **Unexpected slowness** + → Make sure you ran the deployment test, then only your target tests. diff --git a/interactor/config.toml b/interactor/config.toml index 97acd5a5c..dfc1ba62a 100644 --- a/interactor/config.toml +++ b/interactor/config.toml @@ -1,7 +1,2 @@ - -# chain_type = 'simulator' -# gateway_uri = 'http://localhost:8085' - chain_type = 'real' gateway_uri = 'https://devnet-gateway.multiversx.com' - diff --git a/interactor/set_state.json b/interactor/set_state.json deleted file mode 100644 index 86ac79c48..000000000 --- a/interactor/set_state.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "address": "erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa", - "nonce": 6221, - "balance": "128272570000000000000", - "pairs": { - - "454c524f4e44657364744c5453542d346638343965": "120e007e37be2022c0914b2680000000", - "454c524f4e4465736474475245454e2d306531363163": "120e007e37be2022c0914b2680000000", - "454c524f4e4465736474494e5445524e532d656161643135": "120e007e37be2022c0914b2680000000" - }, - "code": "", - "code_hash": "", - "root_hash": "bm7koGXVtATCN5jJdsU2nmEx9MQGQ3Szb9Gq/Yb7Di0=", - "code_metadata": "", - "owner_address": "", - "developer_reward": "0" - } -] \ No newline at end of file diff --git a/interactor/src/complete_flows/complete_flows_interactor_main.rs b/interactor/src/complete_flows/complete_flows_interactor_main.rs new file mode 100644 index 000000000..7722bd994 --- /dev/null +++ b/interactor/src/complete_flows/complete_flows_interactor_main.rs @@ -0,0 +1,301 @@ +#![allow(non_snake_case)] + +use common_interactor::interactor_common_state::CommonState; +use common_interactor::interactor_helpers::InteractorHelpers; +use common_interactor::interactor_state::{EsdtTokenInfo, State}; +use common_interactor::interactor_structs::{ActionConfig, BalanceCheckConfig}; +use common_interactor::{ + common_sovereign_interactor::CommonInteractorTrait, interactor_config::Config, +}; +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::constants::{ + DEPOSIT_EVENT, INTERACTOR_WORKING_DIR, MULTI_ESDT_NFT_TRANSFER_EVENT, SHARD_1, + SOVEREIGN_RECEIVER_ADDRESS, TOKEN_DISPLAY_NAME, TOKEN_TICKER, +}; +use common_test_setup::log; +use cross_chain::DEFAULT_ISSUE_COST; +use error_messages::EXPECTED_MAPPED_TOKEN; +use multiversx_sc::api::{ESDT_LOCAL_MINT_FUNC_NAME, ESDT_NFT_CREATE_FUNC_NAME}; +use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; +use multiversx_sc_snippets::imports::*; +use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use structs::fee::FeeStruct; +use structs::generate_hash::GenerateHash; +use structs::operation::OperationData; +use structs::{OperationHashStatus, RegisterTokenOperation}; + +pub struct CompleteFlowInteract { + pub interactor: Interactor, + pub user_address: Address, + pub state: State, + pub common_state: CommonState, +} + +impl InteractorHelpers for CompleteFlowInteract { + fn interactor(&mut self) -> &mut Interactor { + &mut self.interactor + } + + fn state(&mut self) -> &mut State { + &mut self.state + } + + fn common_state(&mut self) -> &mut CommonState { + &mut self.common_state + } + + fn user_address(&self) -> &Address { + &self.user_address + } +} +impl CommonInteractorTrait for CompleteFlowInteract {} + +impl CompleteFlowInteract { + pub async fn new(config: Config) -> Self { + let mut interactor = Self::initialize_interactor(config.clone()).await; + + interactor.register_wallets().await; + + match config.use_chain_simulator() { + true => { + interactor.initialize_tokens_in_wallets().await; + } + false => { + println!("Skipping token initialization for real network"); + } + } + interactor + } + + async fn initialize_interactor(config: Config) -> Self { + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); + + let current_working_dir = INTERACTOR_WORKING_DIR; + interactor.set_current_dir_from_workspace(current_working_dir); + + let user_address = interactor.register_wallet(test_wallets::grace()).await; + + interactor.generate_blocks_until_all_activations().await; + + CompleteFlowInteract { + interactor, + user_address, + state: State::default(), + common_state: CommonState::load_state(), + } + } + + async fn initialize_tokens_in_wallets(&mut self) { + let token_configs = [ + ("MVX", EsdtTokenType::Fungible, 18), + ("FEE", EsdtTokenType::Fungible, 18), + ("NFT", EsdtTokenType::NonFungibleV2, 0), + ("SFT", EsdtTokenType::SemiFungible, 0), + ("DYN", EsdtTokenType::DynamicNFT, 10), + ("META", EsdtTokenType::MetaFungible, 0), + ("DYNS", EsdtTokenType::DynamicSFT, 18), + ("DYNM", EsdtTokenType::DynamicMeta, 18), + ("TRUSTED", EsdtTokenType::Fungible, 18), + ]; + + for (ticker, token_type, decimals) in token_configs { + self.create_token_with_config(token_type, ticker, decimals) + .await; + } + } + + pub async fn deposit_wrapper( + &mut self, + config: ActionConfig, + token: Option, + fee: Option>, + ) { + let expected_log = self.build_expected_deposit_log(config.clone(), token.clone()); + let payment_vec = self.prepare_deposit_payments( + token.clone(), + fee.clone(), + config.with_transfer_data.unwrap_or_default(), + ); + + let transfer_data = + self.prepare_transfer_data(config.with_transfer_data.unwrap_or_default()); + + self.deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + config.shard, + transfer_data, + payment_vec.clone(), + None, + Some(expected_log), + ) + .await; + + let amount = token.as_ref().map(|t| t.amount.clone()).unwrap_or_default(); + + let balance_config = BalanceCheckConfig::new() + .shard(config.shard) + .token(token.clone()) + .amount(amount) + .fee(fee.clone()) + .with_transfer_data(config.with_transfer_data.unwrap_or_default()) + .is_execute(false); + + self.check_balances_after_action(balance_config).await; + self.update_fee_market_balance_state(fee, payment_vec, config.shard) + .await; + } + + async fn register_and_execute_operation( + &mut self, + config: ActionConfig, + token: Option, + expected_logs: Vec>, + ) { + let operation = self + .prepare_operation(config.shard, token, config.endpoint.as_deref()) + .await; + + let operation_hash = self.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let operations_hashes = + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + self.register_operation(config.shard, &hash_of_hashes, operations_hashes) + .await; + + let expected_operation_hash_status = OperationHashStatus::NotLocked; + + self.check_registered_operation_status( + config.shard, + &hash_of_hashes, + operation_hash.clone(), + expected_operation_hash_status, + ) + .await; + + let caller = self.get_bridge_service_for_shard(config.shard); + self.execute_operations_in_mvx_esdt_safe( + caller, + config.shard, + hash_of_hashes.clone(), + operation.clone(), + expected_logs, + ) + .await; + } + + pub async fn execute_wrapper( + &mut self, + config: ActionConfig, + token: Option, + expected_logs: Vec>, + ) -> Option { + self.register_and_execute_operation(config.clone(), token.clone(), expected_logs) + .await; + + let (expected_token, expected_amount) = match &token { + Some(t) if self.is_sovereign_token(t) => { + let mapped_token = self.get_mapped_token(config.clone(), t, &t.amount).await; + (Some(mapped_token.clone()), mapped_token.amount) + } + _ => { + let amount = token.as_ref().map(|t| t.amount.clone()).unwrap_or_default(); + (token.clone(), amount) + } + }; + + let balance_config = BalanceCheckConfig::new() + .shard(config.shard) + .token(expected_token.clone()) + .amount(expected_amount) + .is_execute(true) + .with_transfer_data(config.with_transfer_data.unwrap_or_default()) + .expected_error(config.expected_log_error); + + self.check_balances_after_action(balance_config).await; + + expected_token + } + + async fn register_sovereign_token(&mut self, shard: u32, token: EsdtTokenInfo) -> String { + let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let nonce = self + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()); + + let operation = RegisterTokenOperation { + token_id: token.token_id.clone(), + token_type: token.token_type, + token_display_name: ManagedBuffer::from(TOKEN_DISPLAY_NAME), + token_ticker: ManagedBuffer::from( + token + .token_id + .into_managed_buffer() + .to_string() + .split('-') + .nth(1) + .unwrap_or(TOKEN_TICKER), + ), + num_decimals: token.decimals, + data: OperationData::new(nonce, self.user_address().into(), None), + }; + + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let operations_hashes = + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + self.register_operation(shard, &hash_of_hashes, operations_hashes) + .await; + + self.register_token(shard, operation).await + } + + pub async fn register_and_execute_sovereign_token( + &mut self, + mut config: ActionConfig, + token: EsdtTokenInfo, + expected_logs: Vec>, + ) -> EsdtTokenInfo { + let expected_deposit_logs = if config.shard == SHARD_1 { + vec![ + log!(MULTI_ESDT_NFT_TRANSFER_EVENT, topics: [EGLD_000000_TOKEN_IDENTIFIER]), + log!(DEPOSIT_EVENT, topics: [DEPOSIT_EVENT]), + ] + } else { + vec![] + }; + + self.deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + config.shard, + OptionalValue::None, + ManagedVec::from_single_item(EgldOrEsdtTokenPayment::egld_payment( + DEFAULT_ISSUE_COST.into(), + )), + None, + Some(expected_deposit_logs), + ) + .await; + + let token_id = self + .register_sovereign_token(config.shard, token.clone()) + .await; + + let additional_log = if token.token_type != EsdtTokenType::Fungible { + log!(ESDT_NFT_CREATE_FUNC_NAME, topics: [token_id.clone()]) + } else { + log!(ESDT_LOCAL_MINT_FUNC_NAME, topics: [token_id]) + }; + + config = config.additional_logs(vec![additional_log]); + + self.execute_wrapper(config, Some(token.clone()), expected_logs) + .await + .expect(EXPECTED_MAPPED_TOKEN) + } +} diff --git a/interactor/src/complete_flows/mod.rs b/interactor/src/complete_flows/mod.rs new file mode 100644 index 000000000..0c08715bc --- /dev/null +++ b/interactor/src/complete_flows/mod.rs @@ -0,0 +1 @@ +pub mod complete_flows_interactor_main; diff --git a/interactor/src/interact.rs b/interactor/src/interact.rs index c914cc9de..05508bc7d 100644 --- a/interactor/src/interact.rs +++ b/interactor/src/interact.rs @@ -1,118 +1,5 @@ -#![allow(non_snake_case)] - -pub mod config; +pub mod complete_flows; pub mod mvx_esdt_safe; -use config::Config; -use multiversx_sc_snippets::imports::*; -use mvx_esdt_safe::mvx_esdt_safe_interactor_main::MvxEsdtSafeInteract; -use serde::{Deserialize, Serialize}; -use std::{ - io::{Read, Write}, - path::Path, -}; - -const STATE_FILE: &str = "state.toml"; - -pub async fn mvx_esdt_safe_cli() { - env_logger::init(); - - let mut args = std::env::args(); - let _ = args.next(); - let cmd = args.next().expect("at least one argument required"); - let config = Config::new(); - let mut interact = MvxEsdtSafeInteract::new(config).await; - match cmd.as_str() { - "upgrade" => interact.upgrade().await, - "pause" => interact.pause_endpoint().await, - "unpause" => interact.unpause_endpoint().await, - "isPaused" => interact.paused_status().await, - "setMaxBridgedAmount" => interact.set_max_bridged_amount().await, - "getMaxBridgedAmount" => interact.max_bridged_amount().await, - _ => panic!("unknown command: {}", &cmd), - } -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct State { - pub mvx_esdt_safe_address: Option, - pub header_verfier_address: Option, - pub fee_market_address: Option, - pub testing_sc_address: Option, - pub chain_config_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() - } - } - - /// Sets the contract addresses - pub fn set_mvx_esdt_safe_contract_address(&mut self, address: Bech32Address) { - self.mvx_esdt_safe_address = Some(address); - } - - pub fn set_header_verifier_address(&mut self, address: Bech32Address) { - self.header_verfier_address = Some(address); - } - - pub fn set_fee_market_address(&mut self, address: Bech32Address) { - self.fee_market_address = Some(address); - } - - pub fn set_testing_sc_address(&mut self, address: Bech32Address) { - self.testing_sc_address = Some(address); - } - - pub fn set_chain_config_sc_address(&mut self, address: Bech32Address) { - self.chain_config_sc_address = Some(address); - } - - /// Returns the contract addresses - pub fn current_mvx_esdt_safe_contract_address(&self) -> &Bech32Address { - self.mvx_esdt_safe_address - .as_ref() - .expect("no known contract, deploy first") - } - - pub fn current_header_verifier_address(&self) -> &Bech32Address { - self.header_verfier_address - .as_ref() - .expect("no known header verifier contract, deploy first") - } - - pub fn current_fee_market_address(&self) -> &Bech32Address { - self.fee_market_address - .as_ref() - .expect("no known fee market contract, deploy first") - } - - pub fn current_testing_sc_address(&self) -> &Bech32Address { - self.testing_sc_address - .as_ref() - .expect("no known testing SC contract, deploy first") - } - - pub fn current_chain_config_sc_address(&self) -> &Bech32Address { - self.chain_config_sc_address - .as_ref() - .expect("no known chain config SC 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(); - } -} +#[allow(dead_code)] +fn main() {} diff --git a/interactor/src/interactor_main.rs b/interactor/src/interactor_main.rs deleted file mode 100644 index 1fddeb634..000000000 --- a/interactor/src/interactor_main.rs +++ /dev/null @@ -1,9 +0,0 @@ - -use multiversx_sc_snippets::imports::*; -use rust_interact::mvx_esdt_safe_cli; - -#[tokio::main] -async fn main() { - mvx_esdt_safe_cli().await; -} - diff --git a/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs b/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs index 4d72ca6b9..4e58a1e9b 100644 --- a/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs +++ b/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs @@ -1,342 +1,127 @@ -use base64::engine::general_purpose::STANDARD as BASE64; -use base64::Engine; -use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; -use multiversx_sc_snippets::multiversx_sc_scenario::scenario_model::{Log, TxResponseStatus}; -use multiversx_sc_snippets::sdk::gateway::SetStateAccount; -use multiversx_sc_snippets::{hex, imports::*}; -use proxies::chain_config_proxy::ChainConfigContractProxy; -use proxies::fee_market_proxy::{FeeMarketProxy, FeeStruct}; -use proxies::header_verifier_proxy::HeaderverifierProxy; +use common_interactor::{ + common_sovereign_interactor::CommonInteractorTrait, interactor_common_state::CommonState, + interactor_helpers::InteractorHelpers, +}; +use common_test_setup::base_setup::init::ExpectedLogs; +use multiversx_sc_snippets::{ + imports::*, multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256, +}; use proxies::mvx_esdt_safe_proxy::MvxEsdtSafeProxy; -use proxies::testing_sc_proxy::TestingScProxy; -use structs::aliases::{OptionalValueTransferDataTuple, PaymentsVec}; -use structs::configs::{EsdtSafeConfig, SovereignConfig}; -use structs::operation::Operation; +use structs::{ + configs::{EsdtSafeConfig, UpdateEsdtSafeConfigOperation}, + generate_hash::GenerateHash, +}; + +use common_interactor::interactor_config::Config; +use common_interactor::interactor_state::State; -use crate::{config::Config, State}; -use common_test_setup::constants::{ - CHAIN_CONFIG_CODE_PATH, FEE_MARKET_CODE_PATH, HEADER_VERIFIER_CODE_PATH, - MVX_ESDT_SAFE_CODE_PATH, TESTING_SC_CODE_PATH, +use common_test_setup::{ + base_setup::log_validations::assert_expected_logs, + constants::{ + EXECUTED_BRIDGE_OP_EVENT, INTERACTOR_WORKING_DIR, MVX_ESDT_SAFE_CODE_PATH, SHARD_0, + UPDATE_ESDT_SAFE_CONFIG_ENDPOINT, + }, + log, }; -use common_test_setup::RegisterTokenArgs; pub struct MvxEsdtSafeInteract { pub interactor: Interactor, - pub owner_address: Address, pub user_address: Address, pub state: State, + pub common_state: CommonState, } -impl MvxEsdtSafeInteract { - 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("interactor"); - let owner_address = interactor.register_wallet(test_wallets::mike()).await; - let user_address = interactor.register_wallet(test_wallets::bob()).await; - - // Useful in the chain simulator setting - // generate blocks until ESDTSystemSCAddress is enabled - interactor.generate_blocks_until_epoch(1u64).await.unwrap(); - - let set_state_response = interactor.set_state_for_saved_accounts().await; - interactor.generate_blocks(2u64).await.unwrap(); - assert!(set_state_response.is_ok()); - - MvxEsdtSafeInteract { - interactor, - owner_address, - user_address, - state: State::load_state(), - } +impl InteractorHelpers for MvxEsdtSafeInteract { + fn interactor(&mut self) -> &mut Interactor { + &mut self.interactor } - pub fn assert_expected_log(&mut self, logs: Vec, expected_log: &str) { - let expected_bytes = ManagedBuffer::::from(expected_log).to_vec(); - - let found_log = logs.iter().find(|log| { - log.topics.iter().any(|topic| { - if let Ok(decoded_topic) = BASE64.decode(topic) { - decoded_topic == expected_bytes - } else { - false - } - }) - }); - - assert!(found_log.is_some(), "Expected log not found"); + fn state(&mut self) -> &mut State { + &mut self.state } - pub fn assert_expected_error_message( - &mut self, - response: Result<(), TxResponseStatus>, - expected_error_message: Option<&str>, - ) { - match response { - Ok(_) => assert!( - expected_error_message.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => { - assert_eq!(expected_error_message, Some(error.message.as_str())) - } - } + fn common_state(&mut self) -> &mut CommonState { + &mut self.common_state } - // Key and value should be in hex - pub async fn check_account_storage( - &mut self, - address: Address, - wanted_key: &str, - expected_value: Option<&str>, - ) { - let pairs = self.interactor.get_account_storage(&address).await; - - let found_entry = pairs.iter().find(|(key, _)| key.contains(wanted_key)); - - let decoded_key = self.decode_from_hex(wanted_key); - - match expected_value { - Some(expected) => { - assert!( - found_entry.is_some(), - "Expected key containing '{}' (decoded: '{}') was not found in account storage.", - wanted_key, - decoded_key - ); - - let (_, value) = found_entry.unwrap(); + fn user_address(&self) -> &Address { + &self.user_address + } +} +impl CommonInteractorTrait for MvxEsdtSafeInteract {} - let decoded_expected = self.decode_from_hex(expected); +impl MvxEsdtSafeInteract { + pub async fn new(config: Config) -> Self { + let mut interactor = Self::initialize_interactor(config.clone()).await; - let decoded_value = self.decode_from_hex(value); + interactor.register_wallets().await; - assert!( - value.contains(expected), - "Mismatch: expected '{}' (decoded: '{}') to be contained in '{}' (decoded: '{}')", - expected, - decoded_expected, - value, - decoded_value, - ); + match config.use_chain_simulator() { + true => { + interactor.initialize_tokens_in_wallets().await; } - None => { - assert!( - found_entry.is_none(), - "Did not expect to find key containing '{}' (decoded: '{}') in account storage.", - wanted_key, - decoded_key - ); + false => { + println!("Skipping token initialization for real network"); } } - } - pub fn decode_from_hex(&mut self, hex_string: &str) -> String { - let bytes = - hex::decode(hex_string).expect("Failed to decode hex string: invalid hex format"); - String::from_utf8(bytes).expect("Failed to decode UTF-8 string: invalid UTF-8 bytes") + interactor } - pub async fn deploy_mvx_esdt_safe( - &mut self, - header_verifier_address: Bech32Address, - opt_config: OptionalValue>, - ) { - let new_address = self - .interactor - .tx() - .from(&self.owner_address) - .gas(120_000_000u64) - .typed(MvxEsdtSafeProxy) - .init(header_verifier_address, opt_config) - .code(MVX_ESDT_SAFE_CODE_PATH) - .code_metadata(CodeMetadata::all()) - .returns(ReturnsNewAddress) - .run() - .await; - - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_mvx_esdt_safe_contract_address(Bech32Address::from_bech32_string( - new_address_bech32.clone(), - )); - - println!("new mvx esdt safe address: {new_address_bech32}"); - } - - pub async fn deploy_header_verifier(&mut self) { - let new_address = self - .interactor - .tx() - .from(&self.owner_address) - .gas(120_000_000u64) - .typed(HeaderverifierProxy) - .init() - .code(HEADER_VERIFIER_CODE_PATH) - .code_metadata(CodeMetadata::all()) - .returns(ReturnsNewAddress) - .run() - .await; - - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_header_verifier_address(Bech32Address::from_bech32_string( - new_address_bech32.clone(), - )); - - println!("new header verifier address: {new_address_bech32}"); - } - - pub async fn deploy_fee_market( - &mut self, - esdt_safe_address: Bech32Address, - fee: Option>, - ) { - let new_address = self - .interactor - .tx() - .from(&self.owner_address) - .gas(120_000_000u64) - .typed(FeeMarketProxy) - .init(esdt_safe_address, fee) - .code(FEE_MARKET_CODE_PATH) - .code_metadata(CodeMetadata::all()) - .returns(ReturnsNewAddress) - .run() - .await; - - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_fee_market_address(Bech32Address::from_bech32_string( - new_address_bech32.clone(), - )); - - println!("new fee market address: {new_address_bech32}"); - } - - pub async fn deploy_testing_sc(&mut self) { - let new_address = self - .interactor - .tx() - .from(&self.owner_address) - .gas(120_000_000u64) - .typed(TestingScProxy) - .init() - .code(TESTING_SC_CODE_PATH) - .code_metadata(CodeMetadata::all()) - .returns(ReturnsNewAddress) - .run() - .await; + async fn initialize_interactor(config: Config) -> Self { + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_testing_sc_address(Bech32Address::from_bech32_string( - new_address_bech32.clone(), - )); + let working_dir = INTERACTOR_WORKING_DIR; + interactor.set_current_dir_from_workspace(working_dir); - println!("new testing sc address: {new_address_bech32}"); - } + let user_address = interactor.register_wallet(test_wallets::grace()).await; //shard 1 - pub async fn set_esdt_safe_address_in_header_verifier(&mut self) { - let response = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_header_verifier_address()) - .gas(90_000_000u64) - .typed(HeaderverifierProxy) - .set_esdt_safe_address(self.state.current_mvx_esdt_safe_contract_address()) - .returns(ReturnsResultUnmanaged) - .run() - .await; + interactor.generate_blocks_until_all_activations().await; - println!("Result: {response:?}"); + MvxEsdtSafeInteract { + interactor, + user_address, + state: State::default(), + common_state: CommonState::load_state(), + } } - pub async fn deploy_chain_config(&mut self) { - let config = SovereignConfig::default_config(); - let new_address = self - .interactor - .tx() - .from(&self.owner_address) - .gas(120_000_000u64) - .typed(ChainConfigContractProxy) - .init( - config.min_validators as usize, - config.max_validators as usize, - config.min_stake, - self.owner_address.clone(), - MultiValueEncoded::new(), - ) - .code(CHAIN_CONFIG_CODE_PATH) - .code_metadata(CodeMetadata::all()) - .returns(ReturnsNewAddress) - .run() - .await; - - let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_chain_config_sc_address(Bech32Address::from_bech32_string( - new_address_bech32.clone(), - )); - - println!("new chain config sc address: {new_address_bech32}"); - } + async fn initialize_tokens_in_wallets(&mut self) { + let token_configs = [ + ("MVX", EsdtTokenType::Fungible, 18), + ("FEE", EsdtTokenType::Fungible, 18), + ("TRUSTED", EsdtTokenType::Fungible, 18), + ]; - pub async fn deploy_contracts( - &mut self, - esdt_safe_config: OptionalValue>, - fee_struct: Option>, - ) { - self.deploy_header_verifier().await; - self.deploy_mvx_esdt_safe( - self.state.current_header_verifier_address().clone(), - esdt_safe_config, - ) - .await; - self.deploy_fee_market( - self.state.current_mvx_esdt_safe_contract_address().clone(), - fee_struct, - ) - .await; - self.set_fee_market_address(self.state.current_fee_market_address().to_address()) - .await; - self.unpause_endpoint().await; + for (ticker, token_type, decimals) in token_configs { + self.create_token_with_config(token_type, ticker, decimals) + .await; + } } - pub async fn register_operation( - &mut self, - signature: ManagedBuffer, - hash_of_hashes: &ManagedBuffer, - operations_hashes: MultiValueEncoded>, - ) { + pub async fn complete_setup_phase(&mut self, shard: u32) { + let caller = self.get_bridge_owner_for_shard(shard).clone(); self.interactor .tx() - .from(&self.owner_address) - .to(self.state.current_header_verifier_address()) + .from(&caller) + .to(self.common_state.get_mvx_esdt_safe_address(shard).clone()) .gas(90_000_000u64) - .typed(HeaderverifierProxy) - .register_bridge_operations( - signature, - hash_of_hashes, - ManagedBuffer::new(), - ManagedBuffer::new(), - operations_hashes, - ) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() .returns(ReturnsResultUnmanaged) .run() .await; } pub async fn upgrade(&mut self) { - let response = self - .interactor + let caller = self.get_bridge_owner_for_shard(SHARD_0).clone(); + self.interactor .tx() - .to(self.state.current_mvx_esdt_safe_contract_address()) - .from(&self.owner_address) + .to(self.common_state.get_mvx_esdt_safe_address(SHARD_0).clone()) + .from(caller) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) .upgrade() @@ -345,246 +130,83 @@ impl MvxEsdtSafeInteract { .returns(ReturnsResultUnmanaged) .run() .await; - - println!("Result: {response:?}"); } - pub async fn update_configuration(&mut self, new_config: EsdtSafeConfig) { - let response = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(90_000_000u64) - .typed(MvxEsdtSafeProxy) - .update_configuration(new_config) - .returns(ReturnsResultUnmanaged) - .run() - .await; - - println!("Result: {response:?}"); - } - - pub async fn set_fee_market_address(&mut self, fee_market_address: Address) { - let response = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(90_000_000u64) - .typed(MvxEsdtSafeProxy) - .set_fee_market_address(fee_market_address) - .returns(ReturnsResultUnmanaged) - .run() - .await; - - println!("Result: {response:?}"); - } - - pub async fn deposit( - &mut self, - to: Address, - opt_transfer_data: OptionalValueTransferDataTuple, - payments: PaymentsVec, - expected_error_message: Option<&str>, - expected_log: Option<&str>, - ) { - let (response, logs) = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(90_000_000u64) - .typed(MvxEsdtSafeProxy) - .deposit(to, opt_transfer_data) - .payment(payments) - .returns(ReturnsHandledOrError::new()) - .returns(ReturnsLogs) - .run() - .await; - - self.assert_expected_error_message(response, expected_error_message); - - if let Some(expected_log) = expected_log { - self.assert_expected_log(logs, expected_log); - } - } - - pub async fn execute_operations( + pub async fn update_configuration_after_setup_phase( &mut self, - hash_of_hashes: ManagedBuffer, - operation: Operation, - expected_error_message: Option<&str>, - expected_log: Option<&str>, + shard: u32, + esdt_safe_config: EsdtSafeConfig, + expected_log_error: Option<&str>, ) { - let (response, logs) = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(120_000_000u64) - .typed(MvxEsdtSafeProxy) - .execute_operations(hash_of_hashes, operation) - .returns(ReturnsHandledOrError::new()) - .returns(ReturnsLogs) - .run() - .await; - - self.assert_expected_error_message(response, expected_error_message); + let bridge_service = self.get_bridge_service_for_shard(shard); + let mvx_esdt_safe_address = self.common_state.get_mvx_esdt_safe_address(shard).clone(); - if let Some(expected_log) = expected_log { - self.assert_expected_log(logs, expected_log); - } - } + let operation: UpdateEsdtSafeConfigOperation = UpdateEsdtSafeConfigOperation { + esdt_safe_config, + nonce: self + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + }; - pub async fn register_token( - &mut self, - args: RegisterTokenArgs<'_>, - egld_amount: BigUint, - expected_error_message: Option<&str>, - ) { - let response = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(90_000_000u64) - .typed(MvxEsdtSafeProxy) - .register_token( - args.sov_token_id, - args.token_type, - args.token_display_name, - args.token_ticker, - args.num_decimals, - ) - .egld(egld_amount) - .returns(ReturnsHandledOrError::new()) - .run() - .await; + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - self.assert_expected_error_message(response, expected_error_message); - } + let operations_hashes = MultiValueEncoded::from_iter(vec![operation_hash.clone()]); - pub async fn pause_endpoint(&mut self) { - let response = self - .interactor - .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) - .gas(90_000_000u64) - .typed(MvxEsdtSafeProxy) - .pause_endpoint() - .returns(ReturnsResultUnmanaged) - .run() + self.register_operation(shard, &hash_of_hashes, operations_hashes) .await; - println!("Result: {response:?}"); - } - - pub async fn unpause_endpoint(&mut self) { - let response = self + let (response, logs) = self .interactor .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) + .from(bridge_service) + .to(mvx_esdt_safe_address) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) - .unpause_endpoint() - .returns(ReturnsResultUnmanaged) + .update_esdt_safe_config(hash_of_hashes, operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) .run() .await; - println!("Result: {response:?}"); - } - - pub async fn paused_status(&mut self) { - let result_value = self - .interactor - .query() - .to(self.state.current_mvx_esdt_safe_contract_address()) - .typed(MvxEsdtSafeProxy) - .paused_status() - .returns(ReturnsResultUnmanaged) - .run() - .await; + self.assert_expected_error_message(response, None); - println!("Result: {result_value:?}"); + let expected_logs = vec![ + log!(UPDATE_ESDT_SAFE_CONFIG_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_log_error), + ]; + assert_expected_logs(logs, expected_logs); } - pub async fn set_max_bridged_amount(&mut self) { - let token_id = TokenIdentifier::from_esdt_bytes(&b""[..]); - let max_amount = BigUint::::from(0u128); - - let response = self - .interactor + pub async fn set_fee_market_address(&mut self, caller: Address, fee_market_address: Address) { + self.interactor .tx() - .from(&self.owner_address) - .to(self.state.current_mvx_esdt_safe_contract_address()) + .from(caller) + .to(self.common_state.get_mvx_esdt_safe_address(SHARD_0).clone()) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) - .set_max_bridged_amount(token_id, max_amount) + .set_fee_market_address(fee_market_address) .returns(ReturnsResultUnmanaged) .run() .await; - - println!("Result: {response:?}"); } - pub async fn max_bridged_amount(&mut self) { - let token_id = TokenIdentifier::from_esdt_bytes(&b""[..]); - - let result_value = self - .interactor + pub async fn check_deposited_tokens_amount( + &mut self, + token_id: EgldOrEsdtTokenIdentifier, + shard: u32, + expected_amount: BigUint, + ) { + let mvx_esdt_safe_address = self.common_state.get_mvx_esdt_safe_address(shard).clone(); + let result = self + .interactor() .query() - .to(self.state.current_mvx_esdt_safe_contract_address()) + .to(mvx_esdt_safe_address) .typed(MvxEsdtSafeProxy) - .max_bridged_amount(token_id) - .returns(ReturnsResultUnmanaged) + .deposited_tokens_amount(token_id) + .returns(ReturnsResult) .run() .await; - println!("Result: {result_value:?}"); - } - - //TODO: Make this a common function in common-blackbox-setup - pub fn get_operation_hash( - &mut self, - operation: &Operation, - ) -> ManagedBuffer { - let mut serialized_operation: ManagedBuffer = ManagedBuffer::new(); - let _ = operation.top_encode(&mut serialized_operation); - let sha256 = sha256(&serialized_operation.to_vec()); - - ManagedBuffer::new_from_bytes(&sha256) - } - - pub async fn reset_state_chain_sim(&mut self, address_states: Option>) { - let mut state_vec = vec![ - SetStateAccount::from_address( - Bech32Address::from(self.owner_address.clone()).to_bech32_string(), - ), - SetStateAccount::from_address( - Bech32Address::from(self.user_address.clone()).to_bech32_string(), - ), - SetStateAccount::from_address( - self.state - .current_mvx_esdt_safe_contract_address() - .to_bech32_string(), - ), - SetStateAccount::from_address( - self.state - .current_header_verifier_address() - .to_bech32_string(), - ), - ]; - - if let Some(address_states) = address_states { - for address in address_states { - state_vec.push(SetStateAccount::from_address(address.to_bech32_string())); - } - } - let response = self.interactor.set_state_overwrite(state_vec).await; - self.interactor.generate_blocks(2u64).await.unwrap(); - assert!(response.is_ok()); + assert_eq!(result, expected_amount, "Incorrect deposited tokens amount"); } } diff --git a/interactor/tests/always_deploy_setup_first.rs b/interactor/tests/always_deploy_setup_first.rs new file mode 100644 index 000000000..19e2c3e26 --- /dev/null +++ b/interactor/tests/always_deploy_setup_first.rs @@ -0,0 +1,27 @@ +use common_interactor::common_sovereign_interactor::CommonInteractorTrait; +use common_interactor::interactor_config::Config; +use common_test_setup::constants::DEPLOY_COST; +use multiversx_sc::imports::OptionalValue; +use multiversx_sc_snippets::imports::tokio; +use rust_interact::mvx_esdt_safe::mvx_esdt_safe_interactor_main::MvxEsdtSafeInteract; +use serial_test::serial; +use structs::configs::SovereignConfig; + +/// ### SETUP +/// DEPLOY_CONTRACTS +/// +/// ### ACTION +/// Deploys and completes setup phases for all smart contracts +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn deploy_setup() { + let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; + chain_interactor + .deploy_and_complete_setup_phase( + OptionalValue::Some(DEPLOY_COST.into()), + OptionalValue::Some(SovereignConfig::default_config_for_test()), + OptionalValue::None, + ) + .await; +} diff --git a/interactor/tests/complete_flow_tests.rs b/interactor/tests/complete_flow_tests.rs new file mode 100644 index 000000000..b3c761a6e --- /dev/null +++ b/interactor/tests/complete_flow_tests.rs @@ -0,0 +1,668 @@ +use common_interactor::common_sovereign_interactor::CommonInteractorTrait; +use common_interactor::interactor_config::Config; +use common_interactor::interactor_helpers::InteractorHelpers; +use common_interactor::interactor_state::EsdtTokenInfo; +use common_interactor::interactor_structs::ActionConfig; +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::constants::EXECUTE_OPERATION_ENDPOINT; +use common_test_setup::constants::{ + EXECUTED_BRIDGE_OP_EVENT, ONE_HUNDRED_TOKENS, READ_NATIVE_TOKEN_TESTING_SC_ENDPOINT, SHARD_0, + SHARD_1, TESTING_SC_ENDPOINT, WRONG_ENDPOINT_NAME, +}; +use common_test_setup::log; +use multiversx_sc::types::BigUint; +use multiversx_sc::types::EgldOrEsdtTokenIdentifier; +use multiversx_sc::types::EsdtTokenType; +use multiversx_sc::types::ManagedAddress; +use multiversx_sc::types::ManagedBuffer; +use multiversx_sc::types::ManagedVec; +use multiversx_sc::types::MultiValueEncoded; +use multiversx_sc_scenario::imports::OptionalValue; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_snippets::imports::{tokio, StaticApi}; +use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::vm_err_msg::FUNCTION_NOT_FOUND; +use rstest::rstest; +use rust_interact::complete_flows::complete_flows_interactor_main::CompleteFlowInteract; +use serial_test::serial; +use structs::operation::Operation; +use structs::operation::OperationData; +use structs::operation::TransferData; +use structs::OperationHashStatus; + +//NOTE: The chain sim environment can not handle storage reads from other shards + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit with transfer data only +/// +/// ### EXPECTED +/// Deposit is successful and the event is found in logs +#[rstest] +#[case::sync_to_sync(SHARD_0)] +#[case::sync_to_async(SHARD_1)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_complete_deposit_flow_no_fee_only_transfer_data(#[case] shard: u32) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + None, + None, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit with fee and transfer data only +/// +/// ### EXPECTED +/// Deposit is successful and the event is found in logs +#[rstest] +#[case::sync_to_sync(SHARD_0)] +#[case::sync_to_async(SHARD_1)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_complete_deposit_flow_with_fee_only_transfer_data(#[case] shard: u32) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + let fee = chain_interactor.create_standard_fee(); + + chain_interactor.set_fee_wrapper(fee.clone(), shard).await; + + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + None, + Some(fee), + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-EXEC-FLOW_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract +#[rstest] +#[case::sync_to_sync(SHARD_0)] +#[case::sync_to_async(SHARD_1)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_complete_execute_flow_with_transfer_data_only_success(#[case] shard: u32) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let expected_logs = if shard == SHARD_0 { + vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] + } else { + vec![] + }; + + chain_interactor + .execute_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + None, + expected_logs, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-EXEC-FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with invalid endpoint in operation +/// +/// ### EXPECTED +/// The operation is not executed in the testing smart contract +#[rstest] +#[case::sync_to_sync(SHARD_0)] +#[case::sync_to_async(SHARD_1)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_complete_execute_flow_with_transfer_data_only_fail(#[case] shard: u32) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let expected_logs = if shard == SHARD_0 { + vec![ + log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(FUNCTION_NOT_FOUND)), + ] + } else { + vec![] + }; + + chain_interactor + .execute_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(WRONG_ENDPOINT_NAME.to_string()) + .expected_log_error(FUNCTION_NOT_FOUND), + None, + expected_logs, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit with fee set +/// +/// ### EXPECTED +/// Deposit is successful and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible)] +#[case::non_fungible(EsdtTokenType::NonFungibleV2)] +#[case::semi_fungible(EsdtTokenType::SemiFungible)] +#[case::meta_fungible(EsdtTokenType::MetaFungible)] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT)] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT)] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_with_fee( + #[case] token_type: EsdtTokenType, + #[values(SHARD_0, SHARD_1)] shard: u32, + #[values(0, 1)] token_index: usize, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + let token = chain_interactor.get_token_by_type(token_type, token_index); + + let fee = chain_interactor.create_standard_fee(); + + chain_interactor.set_fee_wrapper(fee.clone(), shard).await; + + chain_interactor + .deposit_wrapper(ActionConfig::new().shard(shard), Some(token), Some(fee)) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit without fee and execute operation +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible)] +#[case::non_fungible(EsdtTokenType::NonFungibleV2)] +#[case::semi_fungible(EsdtTokenType::SemiFungible)] +#[case::meta_fungible(EsdtTokenType::MetaFungible)] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT)] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT)] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_without_fee_and_execute( + #[case] token_type: EsdtTokenType, + #[values(SHARD_0, SHARD_1)] shard: u32, + #[values(0, 1)] token_index: usize, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + let token = chain_interactor.get_token_by_type(token_type, token_index); + + chain_interactor.remove_fee_wrapper(shard).await; + + chain_interactor + .deposit_wrapper(ActionConfig::new().shard(shard), Some(token.clone()), None) + .await; + + let expected_logs = if shard == SHARD_1 { + vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] + } else { + vec![] + }; + + chain_interactor + .execute_wrapper(ActionConfig::new().shard(shard), Some(token), expected_logs) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-EXECUTE-SOVEREIGN-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call register token, execute operation and deposit sov token +/// +/// ### EXPECTED +/// The deposit is successful and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::non_fungible(EsdtTokenType::NonFungibleV2, BigUint::from(1u64))] +#[case::semi_fungible(EsdtTokenType::SemiFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::meta_fungible(EsdtTokenType::MetaFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT, BigUint::from(1u64))] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_register_execute_and_deposit_sov_token( + #[case] token_type: EsdtTokenType, + #[case] amount: BigUint, + #[values(SHARD_0, SHARD_1)] shard: u32, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); + let token_id = chain_interactor.create_random_sovereign_token_id(shard); + + let sov_token = EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(token_id.as_str()), + nonce, + token_type, + decimals, + amount, + }; + + let expected_logs = if shard == SHARD_1 { + vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] + } else { + vec![] + }; + + let main_token = chain_interactor + .register_and_execute_sovereign_token( + ActionConfig::new().shard(shard), + sov_token.clone(), + expected_logs, + ) + .await; + + let additional_logs = chain_interactor.build_sovereign_deposit_logs(&main_token); + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .expected_deposit_token_log(sov_token.clone()) + .additional_logs(additional_logs), + Some(main_token), + None, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit without fee and transfer data +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible)] +#[case::non_fungible(EsdtTokenType::NonFungibleV2)] +#[case::semi_fungible(EsdtTokenType::SemiFungible)] +#[case::meta_fungible(EsdtTokenType::MetaFungible)] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT)] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT)] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_mvx_token_with_transfer_data( + #[case] token_type: EsdtTokenType, + #[values(SHARD_0, SHARD_1)] shard: u32, + #[values(0, 1)] token_index: usize, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let token = chain_interactor.get_token_by_type(token_type, token_index); + + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + Some(token), + None, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit with fee and transfer data +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible)] +#[case::non_fungible(EsdtTokenType::NonFungibleV2)] +#[case::semi_fungible(EsdtTokenType::SemiFungible)] +#[case::meta_fungible(EsdtTokenType::MetaFungible)] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT)] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT)] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_mvx_token_with_transfer_data_and_fee( + #[case] token_type: EsdtTokenType, + #[values(SHARD_0, SHARD_1)] shard: u32, + #[values(0, 1)] token_index: usize, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + let fee = chain_interactor.create_standard_fee(); + + chain_interactor.set_fee_wrapper(fee.clone(), shard).await; + + let token = chain_interactor.get_token_by_type(token_type, token_index); + + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + Some(token), + Some(fee), + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-DEPOSIT-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call deposit without fee and execute operation with transfer data for various ESDT tokens. +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract and the event is found in logs for all token types. +#[rstest] +#[case::fungible(EsdtTokenType::Fungible)] +#[case::non_fungible(EsdtTokenType::NonFungibleV2)] +#[case::semi_fungible(EsdtTokenType::SemiFungible)] +#[case::meta_fungible(EsdtTokenType::MetaFungible)] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT)] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT)] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_and_execute_with_transfer_data( + #[case] token_type: EsdtTokenType, + #[values(0, 1)] token_index: usize, + #[values(SHARD_0, SHARD_1)] shard: u32, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + let token = chain_interactor.get_token_by_type(token_type, token_index); + + chain_interactor.remove_fee_wrapper(shard).await; + + chain_interactor + .deposit_wrapper(ActionConfig::new().shard(shard), Some(token.clone()), None) + .await; + + let expected_logs = if shard == SHARD_0 { + vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] + } else { + vec![] + }; + + chain_interactor + .execute_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + Some(token.clone()), + expected_logs, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-REGISTER_EXECUTE-FLOW_OK +/// +/// ### ACTION +/// Deploy and complete setup phase, then call register, execute with transfer data and deposit sov token +/// +/// ### EXPECTED +/// The deposit is successful and the event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::non_fungible(EsdtTokenType::NonFungibleV2, BigUint::from(1u64))] +#[case::semi_fungible(EsdtTokenType::SemiFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::meta_fungible(EsdtTokenType::MetaFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT, BigUint::from(1u64))] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_register_execute_with_transfer_data_and_deposit_sov_token( + #[case] token_type: EsdtTokenType, + #[case] amount: BigUint, + #[values(SHARD_0, SHARD_1)] shard: u32, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); + let token_id = chain_interactor.create_random_sovereign_token_id(shard); + + let sov_token = EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(token_id.as_str()), + nonce, + token_type, + decimals, + amount: amount.clone(), + }; + + let expected_logs = if shard == SHARD_0 { + vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] + } else { + vec![] + }; + + let main_token = chain_interactor + .register_and_execute_sovereign_token( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()), + sov_token.clone(), + expected_logs, + ) + .await; + + chain_interactor + .withdraw_from_testing_sc( + main_token.clone(), + main_token.nonce, + main_token.amount.clone(), + ) + .await; + + let additional_log = chain_interactor.build_sovereign_deposit_logs(&main_token); + chain_interactor + .deposit_wrapper( + ActionConfig::new() + .shard(shard) + .with_endpoint(TESTING_SC_ENDPOINT.to_string()) + .expected_deposit_token_log(sov_token) + .additional_logs(additional_log), + Some(main_token.clone()), + None, + ) + .await; +} + +/// ### TEST +/// S-FORGE_COMPLETE-REGISTER_EXECUTE-FLOW_FAIL +/// +/// ### ACTION +/// Deploy and complete setup phase, then call register, execute with transfer data +/// +/// ### EXPECTED +/// The operation is not executed in the testing smart contract and the failed event is found in logs +#[rstest] +#[case::fungible(EsdtTokenType::Fungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::non_fungible(EsdtTokenType::NonFungibleV2, BigUint::from(1u64))] +#[case::semi_fungible(EsdtTokenType::SemiFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::meta_fungible(EsdtTokenType::MetaFungible, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_nft(EsdtTokenType::DynamicNFT, BigUint::from(1u64))] +#[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] +#[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_register_execute_call_failed( + #[case] token_type: EsdtTokenType, + #[case] amount: BigUint, + #[values(SHARD_0, SHARD_1)] shard: u32, +) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(shard).await; + + let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); + let token_id = chain_interactor.create_random_sovereign_token_id(shard); + + let sov_token = EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::from(token_id.as_str()), + nonce, + token_type, + decimals, + amount, + }; + + let expected_logs = if shard == SHARD_0 { + vec![ + log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(FUNCTION_NOT_FOUND)), + ] + } else { + vec![] + }; + + chain_interactor + .register_and_execute_sovereign_token( + ActionConfig::new() + .shard(shard) + .with_endpoint(WRONG_ENDPOINT_NAME.to_string()) + .expected_log_error(FUNCTION_NOT_FOUND), + sov_token, + expected_logs, + ) + .await; +} + +#[rstest] +#[case::async_call(SHARD_1)] +#[case::sync_call(SHARD_0)] +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_execute_operation_transfer_data_only_async_call_in_endpoint(#[case] shard: u32) { + let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(SHARD_1).await; + + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_1) + .clone(); + + let wanted_mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(shard) + .clone(); + + let gas_limit = 90_000_000u64; + let function = ManagedBuffer::::from(READ_NATIVE_TOKEN_TESTING_SC_ENDPOINT); + let args = ManagedVec::>::from(vec![ + ManagedBuffer::new_from_bytes(wanted_mvx_esdt_safe_address.to_address().as_bytes()), + ]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation_data = OperationData::new( + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), + Some(transfer_data), + ); + + let operation = Operation::new( + ManagedAddress::from_address( + &chain_interactor + .common_state() + .current_testing_sc_address() + .to_address(), + ), + ManagedVec::new(), + operation_data, + ); + + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + chain_interactor + .register_operation(SHARD_1, &hash_of_hashes, operations_hashes) + .await; + + let expected_operation_hash_status = OperationHashStatus::NotLocked; + chain_interactor + .check_registered_operation_status( + SHARD_1, + &hash_of_hashes, + operation_hash, + expected_operation_hash_status, + ) + .await; + + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_1) + .clone(); + chain_interactor + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_1, + hash_of_hashes, + operation, + vec![], + ) + .await; +} diff --git a/interactor/tests/mvx_esdt_safe_tests.rs b/interactor/tests/mvx_esdt_safe_tests.rs index a6347290a..16f2f672d 100644 --- a/interactor/tests/mvx_esdt_safe_tests.rs +++ b/interactor/tests/mvx_esdt_safe_tests.rs @@ -1,192 +1,237 @@ +use common_interactor::common_sovereign_interactor::CommonInteractorTrait; +use common_interactor::interactor_config::Config; +use common_interactor::interactor_helpers::InteractorHelpers; +use common_interactor::interactor_state::EsdtTokenInfo; +use common_interactor::interactor_structs::{ActionConfig, BalanceCheckConfig}; +use common_test_setup::base_setup::init::ExpectedLogs; use common_test_setup::constants::{ - FEE_TOKEN, FIRST_TEST_TOKEN, ISSUE_COST, OPERATION_HASH_STATUS_STORAGE_KEY, SECOND_TEST_TOKEN, - SOV_TOKEN, SOV_TO_MVX_TOKEN_STORAGE_KEY, TOKEN_TICKER, + EGLD_0_05, EXECUTED_BRIDGE_OP_EVENT, EXECUTE_BRIDGE_OPS_ENDPOINT, EXECUTE_OPERATION_ENDPOINT, + GAS_LIMIT, MULTI_ESDT_NFT_TRANSFER_EVENT, ONE_HUNDRED_TOKENS, PER_GAS, PER_TRANSFER, SHARD_0, + SHARD_1, SOVEREIGN_RECEIVER_ADDRESS, TEN_TOKENS, TESTING_SC_ENDPOINT, }; -use common_test_setup::RegisterTokenArgs; +use common_test_setup::log; +use cross_chain::MAX_GAS_PER_TRANSACTION; use error_messages::{ - BANNED_ENDPOINT_NAME, GAS_LIMIT_TOO_HIGH, INVALID_TYPE, NOTHING_TO_TRANSFER, - NO_ESDT_SAFE_ADDRESS, PAYMENT_DOES_NOT_COVER_FEE, TOO_MANY_TOKENS, + BANNED_ENDPOINT_NAME, CURRENT_OPERATION_NOT_REGISTERED, DEPOSIT_OVER_MAX_AMOUNT, + ERR_EMPTY_PAYMENTS, GAS_LIMIT_TOO_HIGH, MAX_GAS_LIMIT_PER_TX_EXCEEDED, NOTHING_TO_TRANSFER, + TOKEN_NOT_REGISTERED, }; -use header_verifier::OperationHashStatus; +use multiversx_sc::api::ESDT_LOCAL_MINT_FUNC_NAME; +use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; +use multiversx_sc_snippets::imports::*; use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; -use multiversx_sc_snippets::{hex, imports::*}; -use proxies::fee_market_proxy::{FeeStruct, FeeType}; -use rust_interact::config::Config; use rust_interact::mvx_esdt_safe::mvx_esdt_safe_interactor_main::MvxEsdtSafeInteract; use serial_test::serial; +use std::vec; use structs::aliases::PaymentsVec; -use structs::configs::EsdtSafeConfig; +use structs::configs::{EsdtSafeConfig, MaxBridgedAmount}; use structs::operation::{Operation, OperationData, OperationEsdtPayment, TransferData}; - -// Test that deposit fails when there is nothing to transfer and fee is disabled -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Deposit tokens +use structs::OperationHashStatus; + +//NOTE: The chain sim environment can not handle storage reads from other shards + +/// ### TEST +/// M-ESDT_UPDATE_CONFIG_FAIL +/// +/// ### ACTION +/// Call 'update_configuration()' with invalid config +/// +/// ### EXPECTED +/// Error 'MAX_GAS_LIMIT_PER_TX_EXCEEDED' log #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_nothing_to_transfer_no_fee() { +async fn test_update_invalid_config() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) - .await; + let max_tx_gas_limit = MAX_GAS_PER_TRANSACTION + 1; + let config = EsdtSafeConfig { + max_tx_gas_limit, + ..EsdtSafeConfig::default_config() + }; chain_interactor - .deposit( - chain_interactor.user_address.clone(), - OptionalValue::None, - ManagedVec::new(), - Some(NOTHING_TO_TRANSFER), - None, + .update_configuration_after_setup_phase( + SHARD_1, + config, + Some(MAX_GAS_LIMIT_PER_TX_EXCEEDED), ) .await; - - let address_states = vec![chain_interactor.state.current_fee_market_address().clone()]; - - chain_interactor - .reset_state_chain_sim(Some(address_states)) - .await; } -// Test that deposit fails when the token limit is exceeded and fee is disabled -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Create a payments vector consisting of 11 EsdtTokenPayments -// 7. Deposit tokens +/// ### TEST +/// M-ESDT_DEPOSIT_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with max bridged amount exceeded +/// +/// ### EXPECTED +/// Error 'DEPOSIT_OVER_MAX_AMOUNT' log #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_too_many_tokens_no_fee() { +async fn test_deposit_max_bridged_amount_exceeded() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; + + let config = EsdtSafeConfig { + max_bridged_token_amounts: ManagedVec::from(vec![MaxBridgedAmount { + token_id: chain_interactor.state.get_first_fungible_token_identifier(), + amount: BigUint::default(), + }]), + ..EsdtSafeConfig::default_config() + }; + chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) + .update_configuration_after_setup_phase(SHARD_1, config, None) .await; - let esdt_token_payment = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), + let esdt_token_payment = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_first_fungible_token_identifier(), 0, - BigUint::from(1u64), + BigUint::from(ONE_HUNDRED_TOKENS), ); - let payments_vec = PaymentsVec::from(vec![esdt_token_payment; 11]); + let payments_vec = PaymentsVec::from(vec![esdt_token_payment]); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::None, payments_vec, - Some(TOO_MANY_TOKENS), + Some(DEPOSIT_OVER_MAX_AMOUNT), None, ) .await; - let address_states = vec![chain_interactor.state.current_fee_market_address().clone()]; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; chain_interactor - .reset_state_chain_sim(Some(address_states)) + .update_configuration_after_setup_phase(SHARD_1, EsdtSafeConfig::default_config(), None) .await; } -// Test that deposit works when there is no transfer data and fee is disabled -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Create a payments vector -// 7. Deposit and check logs +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with empty payments_vec and no transfer_data +/// +/// ### EXPECTED +/// Error NOTHING_TO_TRANSFER #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_no_transfer_data_no_fee() { +async fn test_deposit_nothing_to_transfer() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; + chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, + OptionalValue::None, + ManagedVec::new(), + Some(NOTHING_TO_TRANSFER), + None, + ) .await; - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(1u64), - ); + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with no transfer_data +/// +/// ### EXPECTED +/// The deposit is successful +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_deposit_no_transfer_data() { + let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(FEE_TOKEN), + chain_interactor.remove_fee_wrapper(SHARD_1).await; + + let esdt_token_payment_one = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_first_fungible_token_identifier(), 0, - BigUint::from(1u64), + BigUint::from(ONE_HUNDRED_TOKENS), ); - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one]); + let expected_logs = chain_interactor.build_expected_deposit_log( + ActionConfig::new().shard(SHARD_1), + Some(chain_interactor.state.get_first_fungible_token_id()), + ); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::None, payments_vec, None, - Some("deposit"), + Some(expected_logs), ) .await; - let address_states = vec![chain_interactor.state.current_fee_market_address().clone()]; + let first_token_id = chain_interactor.state.get_first_fungible_token_id(); + + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_1) + .token(Some(first_token_id)) + .amount(ONE_HUNDRED_TOKENS.into()); chain_interactor - .reset_state_chain_sim(Some(address_states)) + .check_balances_after_action(balance_config) .await; } -// Test that deposit fails when the gas limit is too high and fee is disabled -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Deploy testing smart contract -// 7. Create a payments vector -// 8. Create transfer data -// 9. Deposit +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with gas limit too high in transfer_data +/// +/// ### EXPECTED +/// Error GAS_LIMIT_TOO_HIGH #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_gas_limit_too_high_no_fee() { +async fn test_deposit_gas_limit_too_high_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let config = EsdtSafeConfig::new(ManagedVec::new(), ManagedVec::new(), 1, ManagedVec::new()); - chain_interactor - .deploy_contracts(OptionalValue::Some(config), None) - .await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; - chain_interactor.deploy_testing_sc().await; + let shard = SHARD_1; + let config = EsdtSafeConfig { + max_tx_gas_limit: 1, + ..EsdtSafeConfig::default_config() + }; - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); + chain_interactor + .update_configuration_after_setup_phase(SHARD_1, config, None) + .await; - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), + let esdt_token_payment_one = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_first_fungible_token_identifier(), 0, - BigUint::from(100u64), + BigUint::from(ONE_HUNDRED_TOKENS), ); - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one]); let gas_limit = 2u64; - let function = ManagedBuffer::::from("hello"); + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( vec![ManagedBuffer::from("1")], )); @@ -194,8 +239,9 @@ async fn deposit_gas_limit_too_high_no_fee() { let transfer_data = MultiValue3::from((gas_limit, function, args)); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + shard, OptionalValue::Some(transfer_data), payments_vec, Some(GAS_LIMIT_TOO_HIGH), @@ -203,62 +249,49 @@ async fn deposit_gas_limit_too_high_no_fee() { ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - ]; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(shard).await; chain_interactor - .reset_state_chain_sim(Some(address_states)) + .update_configuration_after_setup_phase(SHARD_1, EsdtSafeConfig::default_config(), None) .await; } -// Test that deposit fails when the endpoint is banned and fee is disabled -// Steps: -// 1. Create an EsdtSafeConfig with a banned endpoint -// 2. Deploy header verifier smart contract -// 3. Deploy mvx-esdt-safe smart contract -// 4. Deploy fee-market smart contract -// 5. Set fee-market address in mvx-esdt-safe smart contract -// 6. Unpause mvx-esdt-safe smart contract -// 7. Deploy testing smart contract -// 8. Create a payments vector -// 9. Create transfer data that contains the banned endpoint name -// 10. Deposit +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with banned endpoint name in transfer_data +/// +/// ### EXPECTED +/// Error BANNED_ENDPOINT_NAME #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_endpoint_banned_no_fee() { +async fn test_deposit_endpoint_banned_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::from(vec![ManagedBuffer::from("hello")]), - ); - chain_interactor - .deploy_contracts(OptionalValue::Some(config), None) - .await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; - chain_interactor.deploy_testing_sc().await; + let config = EsdtSafeConfig { + banned_endpoints: ManagedVec::from(vec![ManagedBuffer::from(TESTING_SC_ENDPOINT)]), + ..EsdtSafeConfig::default_config() + }; - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); + chain_interactor + .update_configuration_after_setup_phase(SHARD_1, config, None) + .await; - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), + let esdt_token_payment_one = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_first_fungible_token_identifier(), 0, - BigUint::from(100u64), + BigUint::from(ONE_HUNDRED_TOKENS), ); - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one]); let gas_limit = 2u64; - let function = ManagedBuffer::::from("hello"); + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( vec![ManagedBuffer::from("1")], )); @@ -266,8 +299,9 @@ async fn deposit_endpoint_banned_no_fee() { let transfer_data = MultiValue3::from((gas_limit, function, args)); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::Some(transfer_data), payments_vec, Some(BANNED_ENDPOINT_NAME), @@ -275,141 +309,105 @@ async fn deposit_endpoint_banned_no_fee() { ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - ]; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; chain_interactor - .reset_state_chain_sim(Some(address_states)) + .update_configuration_after_setup_phase(SHARD_1, EsdtSafeConfig::default_config(), None) .await; } -// NOTE: Add checks for account storage after finding out how to encode values in state - -// Test that deposit works when there is transfer data and fee is enabled -// Steps: -// 1. Create the FeeStruct -// 2. Deploy header verifier smart contract -// 3. Deploy mvx-esdt-safe smart contract -// 4. Deploy fee-market smart contract -// 5. Set fee-market address in mvx-esdt-safe smart contract -// 6. Unpause mvx-esdt-safe smart contract -// 7. Deploy testing smart contract -// 8. Create a payments vector containing the fee payment as well -// 9. Create transfer data -// 10. Deposit +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and valid payment +/// +/// ### EXPECTED +/// USER's balance is updated #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_fee_enabled() { +async fn test_deposit_fee_enabled() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - - let per_transfer = BigUint::from(1u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FEE_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FEE_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - chain_interactor - .deploy_contracts(OptionalValue::Some(config), Some(fee)) - .await; - chain_interactor.deploy_testing_sc().await; + let fee = chain_interactor.create_standard_fee(); + chain_interactor.set_fee_wrapper(fee.clone(), SHARD_1).await; - let fee_amount = BigUint::from(10_000u64); + let fee_amount = BigUint::from(PER_TRANSFER) + (BigUint::from(GAS_LIMIT) * PER_GAS); - let fee_payment = - EsdtTokenPayment::::new(TokenIdentifier::from(FEE_TOKEN), 0, fee_amount.clone()); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), + let fee_payment = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_fee_token_identifier(), 0, - BigUint::from(100u64), + fee_amount.clone(), ); - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), + let esdt_token_payment_one = EgldOrEsdtTokenPayment::::new( + chain_interactor.state.get_first_fungible_token_identifier(), 0, - BigUint::from(100u64), + BigUint::from(ONE_HUNDRED_TOKENS), ); - let payments_vec = PaymentsVec::from(vec![ - fee_payment, - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); + let payments_vec = PaymentsVec::from(vec![fee_payment, esdt_token_payment_one]); - let gas_limit = 1000u64; - let function = ManagedBuffer::::from("hello"); + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( vec![ManagedBuffer::from("1")], )); - let transfer_data = MultiValue3::from((gas_limit, function, args)); + let transfer_data = MultiValue3::from((GAS_LIMIT, function, args)); + let expected_logs = chain_interactor.build_expected_deposit_log( + ActionConfig::new().shard(SHARD_1), + Some(chain_interactor.state.get_first_fungible_token_id()), + ); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::Some(transfer_data), - payments_vec, + payments_vec.clone(), None, - Some("deposit"), + Some(expected_logs), ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - ]; + let first_token_id = chain_interactor.state.get_first_fungible_token_id(); + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_1) + .token(Some(first_token_id.clone())) + .amount(ONE_HUNDRED_TOKENS.into()) + .fee(Some(fee.clone())) + .with_transfer_data(true); chain_interactor - .reset_state_chain_sim(Some(address_states)) + .check_balances_after_action(balance_config) + .await; + chain_interactor + .update_fee_market_balance_state(Some(fee), payments_vec, SHARD_1) .await; } -// Test that deposit works when there is only transfer data and fee is disabled -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Deploy testing smart contract -// 7. Create transfer data -// 8. Deposit and check logs +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and no payment +/// +/// ### EXPECTED +/// Error ERR_EMPTY_PAYMENTS #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_only_transfer_data_no_fee() { +async fn test_deposit_transfer_data_only_with_fee_nothing_to_transfer() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - chain_interactor - .deploy_contracts(OptionalValue::Some(config), None) - .await; + let fee = chain_interactor.create_standard_fee(); - chain_interactor.deploy_testing_sc().await; + chain_interactor.set_fee_wrapper(fee, SHARD_1).await; let gas_limit = 1000u64; - let function = ManagedBuffer::::from("hello"); + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( vec![ManagedBuffer::from("1")], )); @@ -417,690 +415,903 @@ async fn deposit_only_transfer_data_no_fee() { let transfer_data = MultiValue3::from((gas_limit, function, args)); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::Some(transfer_data), ManagedVec::new(), + Some(ERR_EMPTY_PAYMENTS), None, - Some("scCall"), ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - ]; - - chain_interactor - .reset_state_chain_sim(Some(address_states)) - .await; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; } -// Test that deposit fails when payment does not cover fee -// Steps: -// 1. Create the FeeStruct -// 2. Deploy header verifier smart contract -// 3. Deploy mvx-esdt-safe smart contract -// 4. Deploy fee-market smart contract -// 5. Set fee-market address in mvx-esdt-safe smart contract -// 6. Unpause mvx-esdt-safe smart contract -// 7. Deploy testing smart contract -// 8. Create a payments vector without the fee payment -// 9. Create transfer data -// 10. Deposit +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data only and no payments +/// +/// ### EXPECTED +/// The endpoint is called in the testing smart contract #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deposit_payment_does_not_cover_fee() { +async fn test_deposit_only_transfer_data_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - - let per_transfer = BigUint::from(1u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FIRST_TEST_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FIRST_TEST_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - chain_interactor - .deploy_contracts(OptionalValue::Some(config), Some(fee)) - .await; - - chain_interactor.deploy_testing_sc().await; - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(1u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); + chain_interactor.remove_fee_wrapper(SHARD_1).await; - let payments_vec = PaymentsVec::from(vec![ - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - let gas_limit = 10_000u64; - let function = ManagedBuffer::::from("hello"); + let gas_limit = 1000u64; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( vec![ManagedBuffer::from("1")], )); let transfer_data = MultiValue3::from((gas_limit, function, args)); + let expected_logs = + chain_interactor.build_expected_deposit_log(ActionConfig::new().shard(SHARD_1), None); chain_interactor - .deposit( - chain_interactor.user_address.clone(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, OptionalValue::Some(transfer_data), - payments_vec, - Some(PAYMENT_DOES_NOT_COVER_FEE), + ManagedVec::new(), None, + Some(expected_logs), ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - ]; - - chain_interactor - .reset_state_chain_sim(Some(address_states)) - .await; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; } -// TODO: add deposit_refund_fee test after finding a method to check for balance - -// Test that register token fails when the token type is invalid -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Create the token properties -// 4. Register the token +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with no esdt-safe-address set +/// +/// ### EXPECTED +/// Error NO_ESDT_SAFE_ADDRESS #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn register_token_invalid_type_token() { +async fn test_execute_operation_no_operation_registered() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.deploy_header_verifier().await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; - chain_interactor - .deploy_mvx_esdt_safe( - chain_interactor - .state - .current_header_verifier_address() - .clone(), - OptionalValue::Some(EsdtSafeConfig::default_config()), - ) - .await; + let payment = OperationEsdtPayment::new( + chain_interactor.state.get_first_fungible_token_identifier(), + 0, + EsdtTokenData::default(), + ); - let sov_token_id = TokenIdentifier::from_esdt_bytes(SOV_TOKEN); - let token_type = EsdtTokenType::Invalid; - let token_display_name = "SOVEREIGN"; - let num_decimals = 18; - let token_ticker = TOKEN_TICKER; - let egld_payment = BigUint::from(ISSUE_COST); + let operation_data = OperationData::new( + 1, + ManagedAddress::from_address(&chain_interactor.user_address), + None, + ); + let operation = Operation::new( + ManagedAddress::from_address( + &chain_interactor + .common_state() + .current_testing_sc_address() + .to_address(), + ), + vec![payment].into(), + operation_data, + ); + + let hash_of_hashes = chain_interactor.get_operation_hash(&operation); + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_1) + .clone(); + + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(CURRENT_OPERATION_NOT_REGISTERED)), + ]; chain_interactor - .register_token( - RegisterTokenArgs { - sov_token_id, - token_type, - token_display_name, - token_ticker, - num_decimals, - }, - egld_payment, - Some(INVALID_TYPE), + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_1, + hash_of_hashes, + operation, + expected_logs, ) .await; - chain_interactor.reset_state_chain_sim(None).await; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_1).await; } -// Test that register token works when the token type is fungible -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Create the token properties -// 4. Register the token -// 5. Check that the token ticker exists in sov-to-mvx-token storage +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn register_token_fungible_token() { +async fn test_execute_operation_with_egld_success_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.deploy_header_verifier().await; + chain_interactor.remove_fee_wrapper(SHARD_0).await; + + let token = EsdtTokenInfo { + token_id: EgldOrEsdtTokenIdentifier::egld(), + amount: BigUint::from(EGLD_0_05), + nonce: 0, + decimals: 18, + token_type: EsdtTokenType::Fungible, + }; + + let token_data = EsdtTokenData { + amount: BigUint::from(EGLD_0_05), + ..Default::default() + }; + + let payment = OperationEsdtPayment::new(EgldOrEsdtTokenIdentifier::egld(), 0, token_data); + let mut payment_vec = PaymentsVec::new(); + payment_vec.push(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::egld(), + 0, + BigUint::from(EGLD_0_05), + )); + + let gas_limit = 90_000_000u64; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_0) + .clone(); + let operation_data = OperationData::new( + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), + Some(transfer_data), + ); + + let operation = Operation::new( + ManagedAddress::from_address( + &chain_interactor + .common_state() + .current_testing_sc_address() + .to_address(), + ), + vec![payment].into(), + operation_data, + ); + + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let expected_log = + vec![log!(MULTI_ESDT_NFT_TRANSFER_EVENT, topics: [EGLD_000000_TOKEN_IDENTIFIER])]; chain_interactor - .deploy_mvx_esdt_safe( - chain_interactor - .state - .current_header_verifier_address() - .clone(), - OptionalValue::Some(EsdtSafeConfig::default_config()), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_0, + OptionalValue::None, + payment_vec, + None, + Some(expected_log), ) .await; - let sov_token_id = TokenIdentifier::from_esdt_bytes(SOV_TOKEN); - let token_type = EsdtTokenType::Fungible; - let token_display_name = "GREEN"; - let num_decimals = 18; - let token_ticker = TOKEN_TICKER; - let egld_payment = BigUint::from(ISSUE_COST); + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(token.clone())) + .amount(EGLD_0_05.into()); chain_interactor - .register_token( - RegisterTokenArgs { - sov_token_id, - token_type, - token_display_name, - token_ticker, - num_decimals, - }, - egld_payment, - None, + .check_balances_after_action(balance_config) + .await; + + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + chain_interactor + .register_operation(SHARD_0, &hash_of_hashes, operations_hashes) + .await; + + let expected_operation_hash_status = OperationHashStatus::NotLocked; + chain_interactor + .check_registered_operation_status( + SHARD_0, + &hash_of_hashes, + operation_hash, + expected_operation_hash_status, ) .await; - let encoded_token_ticker = hex::encode(token_ticker); - let encoded_key = &hex::encode(SOV_TO_MVX_TOKEN_STORAGE_KEY); + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_0) + .clone(); + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; chain_interactor - .check_account_storage( - chain_interactor - .state - .current_mvx_esdt_safe_contract_address() - .clone() - .to_address(), - encoded_key, - Some(&encoded_token_ticker), + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_0, + hash_of_hashes, + operation, + expected_logs, ) .await; - chain_interactor.reset_state_chain_sim(None).await; + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(token)) + .amount(EGLD_0_05.into()) + .is_execute(true) + .with_transfer_data(true); + + chain_interactor + .check_balances_after_action(balance_config) + .await; } -// Test that register token works when the token type is non-fungible -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Create the token properties -// 4. Register the token -// 5. Check that the token ticker exists in sov-to-mvx-token storage +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation and no fee +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn register_token_non_fungible_token() { +async fn test_execute_operation_only_transfer_data_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.deploy_header_verifier().await; + chain_interactor.remove_fee_wrapper(SHARD_0).await; + + let gas_limit = 90_000_000u64; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_0) + .clone(); + + let operation_data = OperationData::new( + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), + Some(transfer_data), + ); + + let operation = Operation::new( + ManagedAddress::from_address( + &chain_interactor + .common_state() + .current_testing_sc_address() + .to_address(), + ), + ManagedVec::new(), + operation_data, + ); + + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); chain_interactor - .deploy_mvx_esdt_safe( - chain_interactor - .state - .current_header_verifier_address() - .clone(), - OptionalValue::Some(EsdtSafeConfig::default_config()), - ) + .register_operation(SHARD_0, &hash_of_hashes, operations_hashes) .await; - let sov_token_id = TokenIdentifier::from_esdt_bytes(SOV_TOKEN); - let token_type = EsdtTokenType::NonFungible; - let token_display_name = "SOVEREIGN"; - let num_decimals = 18; - let token_ticker = TOKEN_TICKER; - let egld_payment = BigUint::from(ISSUE_COST); - + let expected_operation_status = OperationHashStatus::NotLocked; chain_interactor - .register_token( - RegisterTokenArgs { - sov_token_id, - token_type, - token_display_name, - token_ticker, - num_decimals, - }, - egld_payment, - None, + .check_registered_operation_status( + SHARD_0, + &hash_of_hashes, + operation_hash, + expected_operation_status, ) .await; - let encoded_token_ticker = hex::encode(token_ticker); - let encoded_key = &hex::encode(SOV_TO_MVX_TOKEN_STORAGE_KEY); - + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_0) + .clone(); + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; chain_interactor - .check_account_storage( - chain_interactor - .state - .current_mvx_esdt_safe_contract_address() - .clone() - .to_address(), - encoded_key, - Some(&encoded_token_ticker), + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_0, + hash_of_hashes, + operation, + expected_logs, ) .await; - chain_interactor.reset_state_chain_sim(None).await; + chain_interactor.check_user_balance_unchanged().await; + chain_interactor.check_contracts_empty(SHARD_0).await; } -// Test that register token works when the token type is dynamic non-fungible -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Create the token properties -// 4. Register the token -// 5. Check that the token ticker exists in sov-to-mvx-token storage +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn register_token_dynamic_non_fungible_token() { +async fn test_execute_operation_native_token_success_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.deploy_header_verifier().await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; + + let token_data = EsdtTokenData { + amount: BigUint::from(TEN_TOKENS), + ..Default::default() + }; + + let native_token = chain_interactor.get_native_token(SHARD_1).await; + + let payment = OperationEsdtPayment::new(native_token.clone(), 0, token_data); + + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_1) + .clone(); + + let gas_limit = 90_000_000u64; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation_data = OperationData::new( + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), + Some(transfer_data), + ); + + let operation = Operation::new( + ManagedAddress::from_address( + &chain_interactor + .common_state() + .current_testing_sc_address() + .to_address(), + ), + vec![payment].into(), + operation_data, + ); + + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); chain_interactor - .deploy_mvx_esdt_safe( - chain_interactor - .state - .current_header_verifier_address() - .clone(), - OptionalValue::Some(EsdtSafeConfig::default_config()), - ) + .register_operation(SHARD_1, &hash_of_hashes, operations_hashes) .await; - let sov_token_id = TokenIdentifier::from_esdt_bytes(SOV_TOKEN); - let token_type = EsdtTokenType::DynamicNFT; - let token_display_name = "SOVEREIGN"; - let num_decimals = 18; - let token_ticker = TOKEN_TICKER; - let egld_payment = BigUint::from(ISSUE_COST); - + let expected_operation_hash_status = OperationHashStatus::NotLocked; chain_interactor - .register_token( - RegisterTokenArgs { - sov_token_id, - token_type, - token_display_name, - token_ticker, - num_decimals, - }, - egld_payment, - None, + .check_registered_operation_status( + SHARD_1, + &hash_of_hashes, + operation_hash, + expected_operation_hash_status, ) .await; - let encoded_token_ticker = hex::encode(token_ticker); - let encoded_key = &hex::encode(SOV_TO_MVX_TOKEN_STORAGE_KEY); + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_1) + .clone(); + + let native_token_info = EsdtTokenInfo { + token_id: native_token.clone(), + amount: BigUint::from(TEN_TOKENS), + nonce: 0, + decimals: 18, + token_type: EsdtTokenType::Fungible, + }; + + let native_token_id = native_token.clone().into_managed_buffer().to_string(); + let expected_logs = vec![log!(ESDT_LOCAL_MINT_FUNC_NAME, topics: [native_token_id])]; chain_interactor - .check_account_storage( - chain_interactor - .state - .current_mvx_esdt_safe_contract_address() - .clone() - .to_address(), - encoded_key, - Some(&encoded_token_ticker), + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_1, + hash_of_hashes, + operation, + expected_logs, ) .await; - chain_interactor.reset_state_chain_sim(None).await; + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_1) + .token(Some(native_token_info)) + .amount(TEN_TOKENS.into()) + .is_execute(true) + .with_transfer_data(true); + + chain_interactor + .check_balances_after_action(balance_config) + .await; } -// Test that execute operation fails when the esdt safe address is not registered -// Steps: -// 1. Deploy header verifier smart contract -// 2. Deploy mvx-esdt-safe smart contract -// 3. Deploy fee-market smart contract -// 4. Set fee-market address in mvx-esdt-safe smart contract -// 5. Unpause mvx-esdt-safe smart contract -// 6. Create a payment for the operation -// 7. Create operation and hash of hashes -// 8. Execute operation -// 9. Check that operation-hash-status storage is empty +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation but no registered sovereign token +/// +/// ### EXPECTED +/// Transaction is successful and the logs contain the TOKEN_NOT_REGISTERED error #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn execute_operation_no_esdt_safe_registered() { +async fn test_execute_operation_sovereign_token_not_registered() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) - .await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; - chain_interactor.deploy_testing_sc().await; + let sov_token_id = chain_interactor.create_random_sovereign_token_id(SHARD_1); + + let token_data = EsdtTokenData { + amount: BigUint::from(TEN_TOKENS), + ..Default::default() + }; let payment = OperationEsdtPayment::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), + EgldOrEsdtTokenIdentifier::esdt(EsdtTokenIdentifier::from_esdt_bytes(sov_token_id.clone())), 0, - EsdtTokenData::default(), + token_data, ); + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_1) + .clone(); + let operation_data = OperationData::new( - 1, - ManagedAddress::from_address(&chain_interactor.owner_address), + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), None, ); let operation = Operation::new( - ManagedAddress::from_address( - &chain_interactor - .state - .current_testing_sc_address() - .to_address(), - ), + SOVEREIGN_RECEIVER_ADDRESS.to_managed_address(), vec![payment].into(), operation_data, ); - let hash_of_hashes = chain_interactor.get_operation_hash(&operation); + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); chain_interactor - .execute_operations( - hash_of_hashes, - operation.clone(), - Some(NO_ESDT_SAFE_ADDRESS), - None, - ) + .register_operation(SHARD_1, &hash_of_hashes, operations_hashes) .await; - let encoded_key = &hex::encode(OPERATION_HASH_STATUS_STORAGE_KEY); + let expected_operation_hash_status = OperationHashStatus::NotLocked; chain_interactor - .check_account_storage( - chain_interactor - .state - .current_header_verifier_address() - .to_address(), - encoded_key, - None, + .check_registered_operation_status( + SHARD_1, + &hash_of_hashes, + operation_hash, + expected_operation_hash_status, ) .await; - let address_states = vec![ - chain_interactor.state.current_testing_sc_address().clone(), - chain_interactor.state.current_fee_market_address().clone(), - ]; + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_1) + .clone(); + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(TOKEN_NOT_REGISTERED)), + ]; chain_interactor - .reset_state_chain_sim(Some(address_states)) + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_1, + hash_of_hashes, + operation, + expected_logs, + ) .await; } -// Test that execute operation works in the happy flow -// Steps: -// 1. Create payment vector -// 2. Create transfer data -// 3. Deploy header verifier smart contract -// 4. Deploy mvx-esdt-safe smart contract -// 5. Deploy fee-market smart contract -// 6. Set fee-market address in mvx-esdt-safe smart contract -// 7. Unpause mvx-esdt-safe smart contract -// 8. Deploy testing smart contract -// 9. Create operation -// 10. Deposit and check logs -// 11. Set mvx-esdt-safe address in header verifier smart contract -// 12. Create operation hashes -// 13. Deploy chain config smart contract -// 14. Register operation -// 15. Check that operation-hash-status storage has value OperationHashStatus::NotLocked -// 16. Execute operation and check logs -// 17. Check that operation-hash-status storage is empty +/// ### TEST +/// M-ESDT_SWITCH_MECHANISM_OK +/// +/// ### ACTION +/// Deposit a trusted token into the MVX ESDT Safe with burn mechanism set up and switch to lock mechanism +/// +/// ### EXPECTED +/// The token is burned and the deposited amount is tracked in storage, then the mechanism is switched #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn execute_operation_success_no_fee() { +async fn test_switch_mechanism_with_deposit() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let token_data = EsdtTokenData { - amount: BigUint::from(10_000_000_000_000_000_000u128), // 10 Tokens - ..Default::default() + + chain_interactor.remove_fee_wrapper(SHARD_0).await; + + let trusted_token = chain_interactor.common_state().get_trusted_token(); + let trusted_token_id = EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()); + let trusted_token_info = EsdtTokenInfo { + token_id: trusted_token_id.clone(), + amount: BigUint::from(ONE_HUNDRED_TOKENS), + nonce: 0, + decimals: 18, + token_type: EsdtTokenType::Fungible, }; - let payment = OperationEsdtPayment::new(TokenIdentifier::from(FIRST_TEST_TOKEN), 0, token_data); - let mut payment_vec = PaymentsVec::new(); - payment_vec.push(EsdtTokenPayment { - token_identifier: TokenIdentifier::from_esdt_bytes(FIRST_TEST_TOKEN), - token_nonce: 0, - amount: BigUint::from(10_000_000_000_000_000_000u128), - }); + chain_interactor + .set_token_burn_mechanism(trusted_token_id.clone(), SHARD_0) + .await; - let gas_limit = 90_000_000u64; - let function = ManagedBuffer::::from("hello"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + let deposit_amount = BigUint::from(ONE_HUNDRED_TOKENS); + let esdt_token_payment = EgldOrEsdtTokenPayment::::new( + trusted_token_id.clone(), + 0, + deposit_amount.clone(), + ); - let transfer_data = TransferData::new(gas_limit, function, args); + let payments_vec = PaymentsVec::from(vec![esdt_token_payment]); - let operation_data = OperationData::new( - 1, - ManagedAddress::from_address(&chain_interactor.owner_address), - Some(transfer_data), + let expected_logs = chain_interactor.build_expected_deposit_log( + ActionConfig::new().shard(SHARD_0), + Some(trusted_token_info.clone()), ); + chain_interactor + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_0, + OptionalValue::None, + payments_vec.clone(), + None, + Some(expected_logs.clone()), + ) + .await; + + chain_interactor + .common_state() + .add_to_deposited_amount(deposit_amount.clone()); + + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(trusted_token_info.clone())) + .amount(deposit_amount.clone()) + .is_burn_mechanism_set(true); chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) + .check_balances_after_action(balance_config) .await; - chain_interactor.deploy_testing_sc().await; + chain_interactor + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, deposit_amount.clone()) + .await; - let operation = Operation::new( - ManagedAddress::from_address( - &chain_interactor - .state - .current_testing_sc_address() - .to_address(), - ), - vec![payment].into(), - operation_data, - ); + // === Switch to Lock Mechanism === - let operation_hash = chain_interactor.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + chain_interactor + .set_token_lock_mechanism(trusted_token_id.clone(), SHARD_0) + .await; chain_interactor - .deposit( - chain_interactor - .state - .current_mvx_esdt_safe_contract_address() - .to_address(), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_0, OptionalValue::None, - payment_vec, + payments_vec, None, - Some("deposit"), + Some(expected_logs), ) .await; chain_interactor - .set_esdt_safe_address_in_header_verifier() + .common_state() + .add_to_deposited_amount(deposit_amount.clone()); + + // Since the mechanism was switched, the trusted token amount was minted in the sc, now we check for both the mint and the new deposit amount + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(trusted_token_info)) + .amount(BigUint::from(2u64) * deposit_amount); + + chain_interactor + .check_balances_after_action(balance_config) .await; - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + chain_interactor + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, BigUint::zero()) + .await; +} - chain_interactor.deploy_chain_config().await; +/// ### TEST +/// M-ESDT_EXEC_WITH_BURN_MECHANISM_OK +/// +/// ### ACTION +/// Execute an operation with a trusted token with burn mechanism set up +/// +/// ### EXPECTED +/// The operation is executed successfully and the deposited amount is updated in storage +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_execute_operation_with_burn_mechanism() { + let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; + + chain_interactor.remove_fee_wrapper(SHARD_0).await; + + let trusted_token = chain_interactor.common_state().get_trusted_token(); + let trusted_token_id = EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()); + let trusted_token_info = EsdtTokenInfo { + token_id: trusted_token_id.clone(), + amount: BigUint::from(ONE_HUNDRED_TOKENS), + nonce: 0, + decimals: 18, + token_type: EsdtTokenType::Fungible, + }; chain_interactor - .register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes) + .set_token_burn_mechanism(trusted_token_id.clone(), SHARD_0) .await; - let operation_status = OperationHashStatus::NotLocked as u8; - let expected_operation_hash_status = format!("{:02x}", operation_status); - let encoded_key = &hex::encode(OPERATION_HASH_STATUS_STORAGE_KEY); + let deposit_amount = BigUint::from(ONE_HUNDRED_TOKENS); + let esdt_token_payment = EgldOrEsdtTokenPayment::::new( + trusted_token_id.clone(), + 0, + deposit_amount.clone(), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment]); + let expected_logs = chain_interactor.build_expected_deposit_log( + ActionConfig::new().shard(SHARD_0), + Some(trusted_token_info.clone()), + ); chain_interactor - .check_account_storage( - chain_interactor - .state - .current_header_verifier_address() - .to_address(), - encoded_key, - Some(&expected_operation_hash_status), + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_0, + OptionalValue::None, + payments_vec.clone(), + None, + Some(expected_logs), ) .await; chain_interactor - .execute_operations( - hash_of_hashes, - operation.clone(), - None, - Some("executedBridgeOp"), + .common_state() + .add_to_deposited_amount(deposit_amount.clone()); + + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(trusted_token_info.clone())) + .amount(deposit_amount.clone()) + .is_burn_mechanism_set(true); + + chain_interactor + .check_balances_after_action(balance_config) + .await; + + let current_deposited_amount = chain_interactor.common_state().get_deposited_amount(); + + chain_interactor + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, current_deposited_amount) + .await; + + let operation = chain_interactor + .prepare_operation( + SHARD_0, + Some(trusted_token_info.clone()), + Some(TESTING_SC_ENDPOINT), ) .await; + let operation_hash = chain_interactor.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + chain_interactor - .check_account_storage( - chain_interactor - .state - .current_header_verifier_address() - .to_address(), - encoded_key, - None, + .register_operation(SHARD_0, &hash_of_hashes, operations_hashes) + .await; + + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_0) + .clone(); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + chain_interactor + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_0, + hash_of_hashes, + operation, + expected_logs, ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - chain_interactor - .state - .current_chain_config_sc_address() - .clone(), - ]; + chain_interactor + .common_state() + .subtract_from_deposited_amount(deposit_amount.clone()); + + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_0) + .token(Some(trusted_token_info)) + .amount(deposit_amount.clone()) + .is_execute(true) + .with_transfer_data(true); + + chain_interactor + .check_balances_after_action(balance_config) + .await; + + let current_deposited_amount = chain_interactor.common_state().get_deposited_amount(); chain_interactor - .reset_state_chain_sim(Some(address_states)) + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, current_deposited_amount) .await; } -// Test that execute operation works when there is only transfer data and fee is disabled -// Steps: -// 1. Create transfer data -// 2. Deploy header verifier smart contract -// 3. Deploy mvx-esdt-safe smart contract -// 4. Deploy fee-market smart contract -// 5. Set fee-market address in mvx-esdt-safe smart contract -// 6. Unpause mvx-esdt-safe smart contract -// 7. Deploy testing smart contract -// 8. Create operation -// 9. Set mvx-esdt-safe address in header verifier smart contract -// 10. Create operation hashes -// 11. Deploy chain config smart contract -// 12. Register operation -// 13. Check that operation-hash-status storage has value OperationHashStatus::NotLocked -// 14. Execute operation and check logs -// 15. Check that operation-hash-status storage is empty +/// ### TEST +/// M-ESDT_EXEC_AND_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'execute_operation()' for a native token and deposit it +/// +/// ### EXPECTED +/// The native token is minted and then burned via deposit #[tokio::test] #[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn execute_operation_only_transfer_data_no_fee() { +async fn test_execute_and_deposit_with_native_token_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - let gas_limit = 90_000_000u64; - let function = ManagedBuffer::::from("hello"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + chain_interactor.remove_fee_wrapper(SHARD_1).await; - let transfer_data = TransferData::new(gas_limit, function, args); + let token_data = EsdtTokenData { + amount: BigUint::from(TEN_TOKENS), + ..Default::default() + }; - let operation_data = OperationData::new( - 1, - ManagedAddress::from_address(&chain_interactor.owner_address), - Some(transfer_data), - ); + let native_token = chain_interactor.get_native_token(SHARD_1).await; - chain_interactor - .deploy_contracts(OptionalValue::Some(EsdtSafeConfig::default_config()), None) - .await; + let payment = OperationEsdtPayment::new(native_token.clone(), 0, token_data); - chain_interactor.deploy_testing_sc().await; + let mvx_esdt_safe_address = chain_interactor + .common_state + .get_mvx_esdt_safe_address(SHARD_1) + .clone(); + + let operation_data = OperationData::new( + chain_interactor + .common_state() + .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), + ManagedAddress::from_address(&chain_interactor.user_address), + None, + ); let operation = Operation::new( - ManagedAddress::from_address( - &chain_interactor - .state - .current_testing_sc_address() - .to_address(), - ), - ManagedVec::new(), + ManagedAddress::from_address(&chain_interactor.user_address), + vec![payment].into(), operation_data, ); let operation_hash = chain_interactor.get_operation_hash(&operation); let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); chain_interactor - .set_esdt_safe_address_in_header_verifier() + .register_operation(SHARD_1, &hash_of_hashes, operations_hashes) .await; - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - chain_interactor.deploy_chain_config().await; - + let expected_operation_hash_status = OperationHashStatus::NotLocked; chain_interactor - .register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes) + .check_registered_operation_status( + SHARD_1, + &hash_of_hashes, + operation_hash, + expected_operation_hash_status, + ) .await; - let operation_status = OperationHashStatus::NotLocked as u8; - let expected_operation_hash_status = format!("{:02x}", operation_status); - let encoded_key = &hex::encode(OPERATION_HASH_STATUS_STORAGE_KEY); + let bridge_service = chain_interactor + .get_bridge_service_for_shard(SHARD_1) + .clone(); + let native_token_info = EsdtTokenInfo { + token_id: native_token.clone(), + amount: BigUint::from(TEN_TOKENS), + nonce: 0, + decimals: 18, + token_type: EsdtTokenType::Fungible, + }; + + let native_token_id = native_token.clone().into_managed_buffer().to_string(); + let expected_logs = vec![log!(ESDT_LOCAL_MINT_FUNC_NAME, topics: [native_token_id])]; chain_interactor - .check_account_storage( - chain_interactor - .state - .current_header_verifier_address() - .to_address(), - encoded_key, - Some(&expected_operation_hash_status), + .execute_operations_in_mvx_esdt_safe( + bridge_service, + SHARD_1, + hash_of_hashes, + operation, + expected_logs, ) .await; + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_1) + .token(Some(native_token_info.clone())) + .amount(TEN_TOKENS.into()) + .is_execute(true); + chain_interactor - .execute_operations( - hash_of_hashes, - operation.clone(), - None, - Some("executedBridgeOp"), - ) + .check_balances_after_action(balance_config) .await; + let esdt_token_payment_one = + EgldOrEsdtTokenPayment::::new(native_token, 0, BigUint::from(TEN_TOKENS)); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one]); + + let expected_logs = chain_interactor.build_expected_deposit_log( + ActionConfig::new().shard(SHARD_1), + Some(native_token_info.clone()), + ); chain_interactor - .check_account_storage( - chain_interactor - .state - .current_header_verifier_address() - .to_address(), - encoded_key, + .deposit_in_mvx_esdt_safe( + SOVEREIGN_RECEIVER_ADDRESS.to_address(), + SHARD_1, + OptionalValue::None, + payments_vec, None, + Some(expected_logs), ) .await; - let address_states = vec![ - chain_interactor.state.current_fee_market_address().clone(), - chain_interactor.state.current_testing_sc_address().clone(), - chain_interactor - .state - .current_chain_config_sc_address() - .clone(), - ]; + let balance_config = BalanceCheckConfig::new() + .shard(SHARD_1) + .token(Some(native_token_info.clone())) + .amount(TEN_TOKENS.into()); chain_interactor - .reset_state_chain_sim(Some(address_states)) + .check_balances_after_action(balance_config) .await; } +#[tokio::test] +#[serial] +#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] +async fn test_pause_contract() { + let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; + chain_interactor.remove_fee_wrapper(SHARD_1).await; + + chain_interactor.switch_pause_status(true, SHARD_1).await; + + chain_interactor.switch_pause_status(false, SHARD_1).await; +} diff --git a/interactor/wallets/wallet_shard_0.pem b/interactor/wallets/wallet_shard_0.pem new file mode 100644 index 000000000..8cb772228 --- /dev/null +++ b/interactor/wallets/wallet_shard_0.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1jhm6wq5mdq2gn9sk00akgxd8zyuh2055kqdljrp2kdmm67nwyyqqkfqgvj----- +NjBhZDAxMmMyZDVmYmI4MWZlNmRhMmQ3Nzg2OTY2Y2ZiM2VlNTQ4MTI2NzkyN2U5 +N2YwYjZkYjNhMjM0YmEyMTk1ZjdhNzAyOWI2ODE0ODk5NjE2N2JmYjY0MTlhNzEx +Mzk3NTNlOTRiMDFiZjkwYzJhYjM3N2JkN2E2ZTIxMDA= +-----END PRIVATE KEY for erd1jhm6wq5mdq2gn9sk00akgxd8zyuh2055kqdljrp2kdmm67nwyyqqkfqgvj----- \ No newline at end of file diff --git a/mvx-esdt-safe/Cargo.toml b/mvx-esdt-safe/Cargo.toml index 22a205271..ed55cf2cb 100644 --- a/mvx-esdt-safe/Cargo.toml +++ b/mvx-esdt-safe/Cargo.toml @@ -9,19 +9,19 @@ authors = ["you"] path = "src/lib.rs" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dependencies.multiversx-sc-modules] -version = "=0.57.1" +version = "0.63.0" [dependencies.testing-sc] path = "../testing-sc" -[dependencies.utils] -path = "../common/utils" +[dependencies.common-utils] +path = "../common/common-utils" -[dependencies.fee-market] -path = "../fee-market" +[dependencies.mvx-fee-market] +path = "../mvx-fee-market" [dependencies.structs] path = "../common/structs" @@ -29,6 +29,9 @@ path = "../common/structs" [dependencies.cross-chain] path = "../common/cross-chain" +[dependencies.custom-events] +path = "../common/custom-events" + [dependencies.setup-phase] path = "../common/setup-phase" @@ -48,7 +51,8 @@ path = "../common/error-messages" path = "../common/common-test-setup" [dev-dependencies.multiversx-sc-scenario] -version = "0.57.1" +version = "0.63.0" +features = ["bls"] [features] chain-simulator-tests = [] diff --git a/mvx-esdt-safe/README.md b/mvx-esdt-safe/README.md new file mode 100644 index 000000000..d107a8817 --- /dev/null +++ b/mvx-esdt-safe/README.md @@ -0,0 +1,10 @@ +# MultiversX ESDT Safe + +MultiversX-side vault for the bridge. It accepts deposits heading to the sovereign chain, executes signed operations coming from the sovereign side, and manages token registration plus burn/lock mechanics. + +- **Initialization:** `init(sovereign_owner, sovereign_forge_address, sov_token_prefix, opt_config)` sets the sovereign owner/admin, validates the sovereign token prefix, stores config (whitelist/blacklist, gas limits, max amounts), and starts paused. +- **Deposits (MultiversX → Sovereign):** `deposit(to, optTransferData)` enforces pause/blacklists/whitelists, charges bridge fees via the Fee Market, then burns or locks tokens based on the configured mechanism before emitting a deposit event. +- **Execution (Sovereign → MultiversX):** `executeBridgeOps(hashOfHashes, operation)` is called after validators register the operation in the Header Verifier. It locks the operation hash, mints/unlocks tokens (or performs a contract call), and emits completion. Refunds are handled if execution fails. +- **Token management:** `registerToken` issues a wrapped ESDT for a sovereign token ID (requires fee payment). `registerNativeToken` issues the sovereign chain’s native token during setup. Burn/lock mechanism can be toggled via `setTokenBurnMechanism*` and `setTokenLockMechanism*`. +- **Configuration & safety:** Config updates (`updateEsdtSafeConfig*`), pause control (`pauseContract`), and fee market address are gated by setup checks. `completeSetupPhase` unpauses, ensures native token and fee market are set, and hands off control to the Header Verifier. +- **Interactions:** Owned by the Header Verifier after setup. Calls the Fee Market to subtract fees and the Header Verifier to lock/clear operation hashes. diff --git a/mvx-esdt-safe/meta/Cargo.toml b/mvx-esdt-safe/meta/Cargo.toml index daf8741dd..c0c718a09 100644 --- a/mvx-esdt-safe/meta/Cargo.toml +++ b/mvx-esdt-safe/meta/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.57.1" default-features = false +version = "0.63.0" diff --git a/mvx-esdt-safe/sc-config.toml b/mvx-esdt-safe/sc-config.toml index 077ef7702..668d4bf7c 100644 --- a/mvx-esdt-safe/sc-config.toml +++ b/mvx-esdt-safe/sc-config.toml @@ -1,17 +1,2 @@ -[contracts.main] -name = "mvx-esdt-safe" - -[contracts.full] -name = "mvx-esdt-safe-full" -add-unlabelled = true -add-labels = ["mvx-esdt-safe-external-view"] - -[contracts.view] -name = "mvx-esdt-safe-view" -external-view = true -add-unlabelled = false -add-labels = ["mvx-esdt-safe-external-view"] - [[proxy]] path = "../common/proxies/src/mvx_esdt_safe_proxy.rs" - diff --git a/mvx-esdt-safe/src/bridging_mechanism.rs b/mvx-esdt-safe/src/bridging_mechanism.rs index ab28131cc..8dd0e3a16 100644 --- a/mvx-esdt-safe/src/bridging_mechanism.rs +++ b/mvx-esdt-safe/src/bridging_mechanism.rs @@ -1,18 +1,39 @@ use error_messages::{ - MINT_AND_BURN_ROLES_NOT_FOUND, TOKEN_ID_IS_NOT_TRUSTED, TOKEN_IS_FROM_SOVEREIGN, + BURN_MECHANISM_NON_ESDT_TOKENS, ESDT_SAFE_STILL_PAUSED, LOCK_MECHANISM_NON_ESDT, + MINT_AND_BURN_ROLES_NOT_FOUND, SETUP_PHASE_ALREADY_COMPLETED, + TOKEN_ALREADY_REGISTERED_WITH_BURN_MECHANISM, TOKEN_ID_IS_NOT_TRUSTED, + TOKEN_NOT_REGISTERED_WITH_BURN_MECHANISM, }; use multiversx_sc::imports::*; - -pub const TRUSTED_TOKEN_IDS: [&str; 1] = ["USDC-c76f1f"]; +use multiversx_sc_modules::pause; +use structs::{ + configs::{SetBurnMechanismOperation, SetLockMechanismOperation}, + generate_hash::GenerateHash, +}; #[multiversx_sc::module] pub trait BridgingMechanism: - cross_chain::storage::CrossChainStorage + multiversx_sc_modules::only_admin::OnlyAdminModule + cross_chain::storage::CrossChainStorage + + cross_chain::execute_common::ExecuteCommonModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + pause::PauseModule { - #[only_admin] - #[endpoint(setTokenBurnMechanism)] - fn set_token_burn_mechanism(&self, token_id: TokenIdentifier) { - let token_esdt_roles = self.blockchain().get_esdt_local_roles(&token_id); + #[only_owner] + #[endpoint(setTokenBurnMechanismSetupPhase)] + fn set_token_burn_mechanism_setup_phase(&self, token_id: EgldOrEsdtTokenIdentifier) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + let mut burn_mechanism_tokens_mapper = self.burn_mechanism_tokens(); + require!( + !burn_mechanism_tokens_mapper.contains(&token_id), + TOKEN_ALREADY_REGISTERED_WITH_BURN_MECHANISM + ); + let esdt_identifier = token_id.clone().unwrap_esdt(); + let token_esdt_roles = self.blockchain().get_esdt_local_roles(&esdt_identifier); require!( token_esdt_roles.contains(EsdtLocalRoleFlags::MINT) @@ -21,42 +42,130 @@ pub trait BridgingMechanism: ); require!( - TRUSTED_TOKEN_IDS + self.trusted_tokens(self.sovereign_forge_address().get()) .iter() - .any(|trusted_token_id| TokenIdentifier::from(*trusted_token_id) == token_id), + .any(|trusted_token_id| EsdtTokenIdentifier::from(trusted_token_id) == token_id), TOKEN_ID_IS_NOT_TRUSTED ); - if self - .multiversx_to_sovereign_token_id_mapper(&token_id) - .is_empty() - { - self.burn_mechanism_tokens().insert(token_id.clone()); - } - + burn_mechanism_tokens_mapper.insert(token_id.clone()); let sc_balance = self .blockchain() - .get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(token_id.clone()), 0); + .get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(esdt_identifier.clone()), 0); if sc_balance != 0 { self.tx() .to(ToSelf) .typed(UserBuiltinProxy) - .esdt_local_burn(&token_id, 0, &sc_balance) + .esdt_local_burn(&esdt_identifier, 0, &sc_balance) .sync_call(); self.deposited_tokens_amount(&token_id).set(sc_balance); } } - #[only_admin] - #[endpoint(setTokenLockMechanism)] - fn set_token_lock_mechanism(&self, token_id: TokenIdentifier) { + #[endpoint(setTokenBurnMechanism)] + fn set_token_burn_mechanism( + &self, + hash_of_hashes: ManagedBuffer, + set_burn_mechanism_operation: SetBurnMechanismOperation, + ) { + let operation_hash = set_burn_mechanism_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + set_burn_mechanism_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if self.is_paused() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(ESDT_SAFE_STILL_PAUSED.into()), + ); + return; + } + if !set_burn_mechanism_operation.token_id.is_esdt() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(BURN_MECHANISM_NON_ESDT_TOKENS.into()), + ); + return; + } + + let mut burn_mechanism_tokens_mapper = self.burn_mechanism_tokens(); + if burn_mechanism_tokens_mapper.contains(&set_burn_mechanism_operation.token_id) { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(TOKEN_ALREADY_REGISTERED_WITH_BURN_MECHANISM.into()), + ); + return; + } + + let esdt_identifier = set_burn_mechanism_operation.token_id.clone().unwrap_esdt(); + let token_esdt_roles = self.blockchain().get_esdt_local_roles(&esdt_identifier); + + if !(token_esdt_roles.contains(EsdtLocalRoleFlags::MINT) + && token_esdt_roles.contains(EsdtLocalRoleFlags::BURN)) + { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(MINT_AND_BURN_ROLES_NOT_FOUND.into()), + ); + return; + } + if !self + .trusted_tokens(self.sovereign_forge_address().get()) + .iter() + .any(|trusted_token_id| { + EsdtTokenIdentifier::from(trusted_token_id) == set_burn_mechanism_operation.token_id + }) + { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(TOKEN_ID_IS_NOT_TRUSTED.into()), + ); + return; + } + + burn_mechanism_tokens_mapper.insert(set_burn_mechanism_operation.token_id.clone()); + let sc_balance = self + .blockchain() + .get_sc_balance(&EgldOrEsdtTokenIdentifier::esdt(esdt_identifier.clone()), 0); + + if sc_balance == 0 { + self.complete_operation(&hash_of_hashes, &operation_hash, None); + return; + } + + if let Err(error_message) = self.try_esdt_local_burn(&esdt_identifier, 0, &sc_balance) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(error_message)); + return; + } + + self.deposited_tokens_amount(&set_burn_mechanism_operation.token_id) + .set(sc_balance); + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } + + #[only_owner] + #[endpoint(setTokenLockMechanismSetupPhase)] + fn set_token_lock_mechanism_setup_phase(&self, token_id: EgldOrEsdtTokenIdentifier) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); require!( - self.multiversx_to_sovereign_token_id_mapper(&token_id) - .is_empty(), - TOKEN_IS_FROM_SOVEREIGN + self.burn_mechanism_tokens().contains(&token_id), + TOKEN_NOT_REGISTERED_WITH_BURN_MECHANISM ); + require!(token_id.is_esdt(), LOCK_MECHANISM_NON_ESDT); self.burn_mechanism_tokens().swap_remove(&token_id); @@ -66,19 +175,94 @@ pub trait BridgingMechanism: self.tx() .to(ToSelf) .typed(UserBuiltinProxy) - .esdt_local_mint(&token_id, 0, &deposited_amount) + .esdt_local_mint(token_id.clone().unwrap_esdt(), 0, &deposited_amount) .sync_call(); self.deposited_tokens_amount(&token_id).set(BigUint::zero()); } } + #[endpoint(setTokenLockMechanism)] + fn set_token_lock_mechanism( + &self, + hash_of_hashes: ManagedBuffer, + set_lock_mechanism_operation: SetLockMechanismOperation, + ) { + let operation_hash = set_lock_mechanism_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + set_lock_mechanism_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if self.is_paused() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(ESDT_SAFE_STILL_PAUSED.into()), + ); + return; + } + if !set_lock_mechanism_operation.token_id.is_esdt() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(LOCK_MECHANISM_NON_ESDT.into()), + ); + return; + } + + let mut burn_mechanism_tokens_mapper = self.burn_mechanism_tokens(); + if !burn_mechanism_tokens_mapper.contains(&set_lock_mechanism_operation.token_id) { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(TOKEN_NOT_REGISTERED_WITH_BURN_MECHANISM.into()), + ); + return; + } + + burn_mechanism_tokens_mapper.swap_remove(&set_lock_mechanism_operation.token_id); + + let deposited_amount = self + .deposited_tokens_amount(&set_lock_mechanism_operation.token_id) + .get(); + + if deposited_amount == 0 { + self.complete_operation(&hash_of_hashes, &operation_hash, None); + return; + } + + let esdt_identifier = set_lock_mechanism_operation.token_id.clone().unwrap_esdt(); + if let Err(error_message) = self.try_esdt_local_mint(&esdt_identifier, 0, &deposited_amount) + { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(error_message)); + return; + } + + self.deposited_tokens_amount(&set_lock_mechanism_operation.token_id) + .set(BigUint::zero()); + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } + + #[storage_mapper_from_address("trustedTokens")] + fn trusted_tokens( + &self, + sc_address: ManagedAddress, + ) -> UnorderedSetMapper; + + #[storage_mapper("sovereignForgeAddress")] + fn sovereign_forge_address(&self) -> SingleValueMapper; + #[storage_mapper("burnMechanismTokens")] - fn burn_mechanism_tokens(&self) -> UnorderedSetMapper; + fn burn_mechanism_tokens(&self) -> UnorderedSetMapper>; + #[view(getDepositedTokensAmount)] #[storage_mapper("depositedTokensAmount")] fn deposited_tokens_amount( &self, - token_identifier: &TokenIdentifier, + token_identifier: &EgldOrEsdtTokenIdentifier, ) -> SingleValueMapper; } diff --git a/mvx-esdt-safe/src/deposit.rs b/mvx-esdt-safe/src/deposit.rs index bfe156568..a3acb2ec4 100644 --- a/mvx-esdt-safe/src/deposit.rs +++ b/mvx-esdt-safe/src/deposit.rs @@ -1,19 +1,15 @@ multiversx_sc::imports!(); -use error_messages::ESDT_SAFE_STILL_PAUSED; -use structs::{ - aliases::{EventPaymentTuple, OptionalValueTransferDataTuple}, - operation::{OperationData, TransferData}, -}; +use structs::aliases::{EventPaymentTuple, OptionalValueTransferDataTuple}; #[multiversx_sc::module] pub trait DepositModule: crate::bridging_mechanism::BridgingMechanism - + utils::UtilsModule + + common_utils::CommonUtilsModule + + setup_phase::SetupPhaseModule + cross_chain::deposit_common::DepositCommonModule + cross_chain::execute_common::ExecuteCommonModule + cross_chain::storage::CrossChainStorage - + cross_chain::events::EventsModule - + multiversx_sc_modules::only_admin::OnlyAdminModule + + custom_events::CustomEventsModule + multiversx_sc_modules::pause::PauseModule { #[payable] @@ -23,105 +19,61 @@ pub trait DepositModule: to: ManagedAddress, opt_transfer_data: OptionalValueTransferDataTuple, ) { - require!(self.not_paused(), ESDT_SAFE_STILL_PAUSED); - - let (fees_payment, payments) = self - .check_and_extract_fee(opt_transfer_data.is_some()) - .into_tuple(); - - let mut total_tokens_for_fees = 0usize; - let mut event_payments = MultiValueEncoded::new(); - let mut refundable_payments = ManagedVec::::new(); - - for payment in &payments { - self.require_below_max_amount(&payment.token_identifier, &payment.amount); - self.require_token_not_on_blacklist(&payment.token_identifier); - - if !self.is_token_whitelist_empty() - && !self.is_token_whitelisted(&payment.token_identifier) - { - refundable_payments.push(payment.clone()); - continue; - } - total_tokens_for_fees += 1; - - let processed_payment = self.process_payment(&payment); - - event_payments.push(processed_payment); - } - - let option_transfer_data = TransferData::from_optional_value(opt_transfer_data); - - if let Some(transfer_data) = option_transfer_data.as_ref() { - self.require_gas_limit_under_limit(transfer_data.gas_limit); - self.require_endpoint_not_banned(&transfer_data.function); - } - - self.match_fee_payment(total_tokens_for_fees, &fees_payment, &option_transfer_data); - - let caller = self.blockchain().get_caller(); - self.refund_tokens(&caller, refundable_payments); - - let tx_nonce = self.get_and_save_next_tx_id(); - - if payments.is_empty() { - self.sc_call_event( - &to, - OperationData::new(tx_nonce, caller, option_transfer_data), - ); - - return; - } - - self.deposit_event( - &to, - &event_payments, - OperationData::new(tx_nonce, caller, option_transfer_data), - ); + self.require_setup_complete(); + self.deposit_common(to, opt_transfer_data, |payment| { + self.process_payment(payment) + }); } fn process_payment( &self, - payment: &EsdtTokenPayment, + payment: &EgldOrEsdtTokenPayment, ) -> EventPaymentTuple { - let mut token_data = self.blockchain().get_esdt_token_data( - &self.blockchain().get_sc_address(), - &payment.token_identifier, - payment.token_nonce, - ); + let token_identifier = payment.token_identifier.clone(); + let mut token_data = if token_identifier.is_egld() { + EsdtTokenData::default() + } else { + let esdt_id = token_identifier.clone().unwrap_esdt(); + self.blockchain().get_esdt_token_data( + &self.blockchain().get_sc_address(), + &esdt_id, + payment.token_nonce, + ) + }; + token_data.amount = payment.amount.clone(); - let token_mapper = self.multiversx_to_sovereign_token_id_mapper(&payment.token_identifier); - if !token_mapper.is_empty() || self.is_native_token(&payment.token_identifier) { - let sov_token_id = token_mapper.get(); - let sov_token_nonce = - self.burn_mainchain_token(payment.clone(), &token_data.token_type, &sov_token_id); - MultiValue3::from((sov_token_id, sov_token_nonce, token_data)) + let token_mapper = self.multiversx_to_sovereign_token_id_mapper(&token_identifier); + if !token_mapper.is_empty() || self.is_native_token(&token_identifier) { + let sov_token_id = if token_mapper.is_empty() { + token_identifier.clone() + } else { + token_mapper.get() + }; + let sov_token_nonce = self.burn_mainchain_token( + &token_identifier, + payment.token_nonce, + &payment.amount, + &token_data.token_type, + &sov_token_id, + ); + MultiValue3::from((sov_token_id.clone(), sov_token_nonce, token_data)) } else { if self.is_fungible(&token_data.token_type) - && self - .burn_mechanism_tokens() - .contains(&payment.token_identifier) + && self.burn_mechanism_tokens().contains(&token_identifier) { + let esdt_id = token_identifier.clone().unwrap_esdt(); self.tx() .to(ToSelf) .typed(UserBuiltinProxy) - .esdt_local_burn( - payment.token_identifier.clone(), - payment.token_nonce, - payment.amount.clone(), - ) + .esdt_local_burn(esdt_id, payment.token_nonce, payment.amount.clone()) .sync_call(); - self.deposited_tokens_amount(&payment.token_identifier) + self.deposited_tokens_amount(&token_identifier) .update(|amount| *amount += payment.amount.clone()); } - MultiValue3::from(( - payment.token_identifier.clone(), - payment.token_nonce, - token_data, - )) + MultiValue3::from((token_identifier.clone(), payment.token_nonce, token_data)) } } } diff --git a/mvx-esdt-safe/src/execute.rs b/mvx-esdt-safe/src/execute.rs index 25438fef1..8d63047f4 100644 --- a/mvx-esdt-safe/src/execute.rs +++ b/mvx-esdt-safe/src/execute.rs @@ -1,7 +1,13 @@ -use error_messages::ESDT_SAFE_STILL_PAUSED; +use cross_chain::MAX_GAS_PER_TRANSACTION; +use error_messages::{ + CREATE_ESDT_FAILED, DEPOSIT_AMOUNT_NOT_ENOUGH, ESDT_SAFE_STILL_PAUSED, GAS_LIMIT_TOO_HIGH, + NOTHING_TO_TRANSFER, TOKEN_NOT_REGISTERED, +}; +use multiversx_sc_modules::only_admin; use structs::{ aliases::GasLimit, - operation::{Operation, OperationData, OperationEsdtPayment, OperationTuple}, + generate_hash::GenerateHash, + operation::{Operation, OperationData, OperationEsdtPayment, TransferData}, }; multiversx_sc::imports!(); @@ -12,132 +18,148 @@ const ESDT_TRANSACTION_GAS: GasLimit = 5_000_000; pub trait ExecuteModule: crate::bridging_mechanism::BridgingMechanism + crate::register_token::RegisterTokenModule - + utils::UtilsModule - + cross_chain::events::EventsModule + + common_utils::CommonUtilsModule + + setup_phase::SetupPhaseModule + + custom_events::CustomEventsModule + cross_chain::storage::CrossChainStorage + cross_chain::deposit_common::DepositCommonModule + cross_chain::execute_common::ExecuteCommonModule + multiversx_sc_modules::pause::PauseModule - + multiversx_sc_modules::only_admin::OnlyAdminModule + + only_admin::OnlyAdminModule { #[endpoint(executeBridgeOps)] fn execute_operations(&self, hash_of_hashes: ManagedBuffer, operation: Operation) { - require!(self.not_paused(), ESDT_SAFE_STILL_PAUSED); - - let operation_hash = self.calculate_operation_hash(&operation); - - self.lock_operation_hash(&operation_hash, &hash_of_hashes); + let operation_hash = operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + operation.data.op_nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if self.is_paused() { + let refund_result = self.refund_transfers(&operation.tokens, &operation); + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(self.merge_error_if_any(ESDT_SAFE_STILL_PAUSED.into(), refund_result)), + ); + return; + } - let operation_tuple = OperationTuple { - op_hash: operation_hash, - operation: operation.clone(), + if operation.tokens.is_empty() { + if let Err(err_msg) = self.execute_sc_call(&hash_of_hashes, &operation_hash, &operation) + { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(err_msg)); + } + return; }; - if operation.tokens.is_empty() { - self.execute_sc_call(&hash_of_hashes, &operation_tuple); + let minted_operation_tokens = match self.process_operation_payments(&operation) { + Ok(tokens) => tokens, + Err(err_msg) => { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(err_msg)); + return; + } + }; + if let Err(err_msg) = self.distribute_payments( + &hash_of_hashes, + &operation_hash, + &operation, + &minted_operation_tokens, + ) { + let refund_result = self.refund_transfers(&minted_operation_tokens, &operation); + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(self.merge_error_if_any(err_msg, refund_result)), + ); return; } - - if let Some(minted_operation_tokens) = self.mint_tokens(&hash_of_hashes, &operation_tuple) { - self.distribute_payments(&hash_of_hashes, &operation_tuple, &minted_operation_tokens); - } } - fn mint_tokens( + fn process_operation_payments( &self, - hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, - ) -> Option>> { + operation: &Operation, + ) -> Result>, ManagedBuffer> { let mut output_payments = ManagedVec::new(); - - for operation_token in operation_tuple.operation.tokens.iter() { - match self.get_mvx_token_id(&operation_token) { - Some(mvx_token_id) => { - let payment = self.process_resolved_token(&mvx_token_id, &operation_token); - output_payments.push(payment); - } - None => { - if let Some(payment) = self.process_unresolved_token( - hash_of_hashes, - operation_tuple, - &operation_token, - ) { - output_payments.push(payment); + for operation_token in operation.tokens.iter() { + let processing_result = match self.get_mvx_token_id(&operation_token) { + Ok(mvx_token_id) => { + if mvx_token_id.is_none() { + self.process_unresolved_token(&operation_token) } else { - return None; + self.process_resolved_token(&mvx_token_id.unwrap(), &operation_token) } } - } + Err(err_msg) => return Err(err_msg), + }; + match processing_result { + Ok(payment) => output_payments.push(payment), + Err(err_msg) => { + let refund_result = self.refund_transfers(&output_payments, operation); + return Err(self.merge_error_if_any(err_msg, refund_result)); + } + }; } - Some(output_payments) + Ok(output_payments) } fn process_resolved_token( &self, - mvx_token_id: &TokenIdentifier, + mvx_token_id: &EgldOrEsdtTokenIdentifier, operation_token: &OperationEsdtPayment, - ) -> OperationEsdtPayment { + ) -> Result, ManagedBuffer> { + let mut nonce: u64 = 0; if self.is_fungible(&operation_token.token_data.token_type) { - self.mint_fungible_token(mvx_token_id, &operation_token.token_data.amount); - OperationEsdtPayment::new(mvx_token_id.clone(), 0, operation_token.token_data.clone()) + self.try_esdt_local_mint( + &mvx_token_id.clone().unwrap_esdt(), + 0, + &operation_token.token_data.amount, + )?; } else { - let nft_nonce = self.esdt_create_and_update_mapper(mvx_token_id, operation_token); - OperationEsdtPayment::new( - mvx_token_id.clone(), - nft_nonce, - operation_token.token_data.clone(), - ) + nonce = self.esdt_create_and_update_mapper(mvx_token_id, operation_token)?; } + + Ok(OperationEsdtPayment::new( + mvx_token_id.clone(), + nonce, + operation_token.token_data.clone(), + )) } fn process_unresolved_token( &self, - hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, operation_token: &OperationEsdtPayment, - ) -> Option> { - if self.is_fungible(&operation_token.token_data.token_type) - && self - .burn_mechanism_tokens() - .contains(&operation_token.token_identifier) - { + ) -> Result, ManagedBuffer> { + if self.is_burn_mechanism_set(operation_token) { let deposited_mapper = self.deposited_tokens_amount(&operation_token.token_identifier); let deposited_amount = deposited_mapper.get(); if operation_token.token_data.amount > deposited_amount { - self.emit_transfer_failed_events(hash_of_hashes, operation_tuple); - self.remove_executed_hash(hash_of_hashes, &operation_tuple.op_hash); - - return None; + return Err(DEPOSIT_AMOUNT_NOT_ENOUGH.into()); } - deposited_mapper.update(|amount| *amount -= operation_token.token_data.amount.clone()); - self.mint_fungible_token( - &operation_token.token_identifier, + self.try_esdt_local_mint( + &operation_token.token_identifier.clone().unwrap_esdt(), + 0, &operation_token.token_data.amount, - ); + )?; + deposited_mapper.update(|amount| *amount -= operation_token.token_data.amount.clone()); } - Some(operation_token.clone()) - } - - fn mint_fungible_token(&self, token_id: &TokenIdentifier, amount: &BigUint) { - self.tx() - .to(ToSelf) - .typed(UserBuiltinProxy) - .esdt_local_mint(token_id, 0, amount) - .sync_call(); + Ok(operation_token.clone()) } fn esdt_create_and_update_mapper( - self, - mvx_token_id: &TokenIdentifier, + &self, + mvx_token_id: &EgldOrEsdtTokenIdentifier, operation_token: &OperationEsdtPayment, - ) -> u64 { + ) -> Result { let mut nonce = 0; - let current_token_type_ref = &operation_token.token_data.token_type; if self.is_sft_or_meta(current_token_type_ref) { @@ -148,40 +170,42 @@ pub trait ExecuteModule: } if nonce == 0 { - nonce = self.mint_nft_tx(mvx_token_id, &operation_token.token_data); - + let new_nonce = self.create_esdt(mvx_token_id, &operation_token.token_data)?; self.update_esdt_info_mappers( &operation_token.token_identifier, operation_token.token_nonce, mvx_token_id, - nonce, + new_nonce, ); - } else { - self.tx() - .to(ToSelf) - .typed(system_proxy::UserBuiltinProxy) - .esdt_local_mint(mvx_token_id, nonce, &operation_token.token_data.amount) - .sync_call(); + return Ok(new_nonce); } - nonce + self.try_esdt_local_mint( + &mvx_token_id.clone().unwrap_esdt(), + nonce, + &operation_token.token_data.amount, + )?; + + Ok(nonce) } - fn mint_nft_tx( + fn create_esdt( &self, - mvx_token_id: &TokenIdentifier, + token_id: &EgldOrEsdtTokenIdentifier, token_data: &EsdtTokenData, - ) -> u64 { + ) -> Result { let mut amount = token_data.amount.clone(); if self.is_sft_or_meta(&token_data.token_type) { amount += BigUint::from(1u32); } - self.tx() + let esdt_token_id = token_id.clone().unwrap_esdt(); + let result = self + .tx() .to(ToSelf) - .typed(system_proxy::UserBuiltinProxy) + .typed(UserBuiltinProxy) .esdt_nft_create( - mvx_token_id, + esdt_token_id.clone(), &amount, &token_data.name, &token_data.royalties, @@ -189,186 +213,259 @@ pub trait ExecuteModule: &token_data.attributes, &token_data.uris, ) - .returns(ReturnsResult) - .sync_call() + .returns(ReturnsHandledOrError::new().returns(ReturnsResult)) + .sync_call_fallible(); + + match result { + Ok(nonce) => Ok(nonce), + Err(error_code) => { + Err(self.format_error(CREATE_ESDT_FAILED, esdt_token_id, error_code)) + } + } } fn distribute_payments( &self, hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, - tokens_list: &ManagedVec>, - ) { - let mapped_tokens: ManagedVec> = tokens_list - .iter() - .map(|token| token.clone().into()) - .collect(); - - match &operation_tuple.operation.data.opt_transfer_data { + operation_hash: &ManagedBuffer, + operation: &Operation, + output_payments: &ManagedVec>, + ) -> Result<(), ManagedBuffer> { + let payment_tokens: ManagedVec> = + output_payments + .iter() + .map(|token| token.clone().into()) + .collect(); + + match &operation.data.opt_transfer_data { Some(transfer_data) => { + self.validate_transfer_data(transfer_data)?; let args = ManagedArgBuffer::from(transfer_data.args.clone()); self.tx() - .to(&operation_tuple.operation.to) + .to(&operation.to) .raw_call(transfer_data.function.clone()) .arguments_raw(args) - .payment(&mapped_tokens) + .payment(payment_tokens) .gas(transfer_data.gas_limit) - .callback( - ::callbacks(self) - .execute(hash_of_hashes, operation_tuple), - ) + .callback(::callbacks(self).execute( + hash_of_hashes, + operation_hash, + operation, + output_payments, + )) .gas_for_callback(CALLBACK_GAS) .register_promise(); } None => { self.tx() - .to(&operation_tuple.operation.to) - .multi_esdt(mapped_tokens) + .to(&operation.to) + .payment(&payment_tokens) .gas(ESDT_TRANSACTION_GAS) - .callback( - ::callbacks(self) - .execute(hash_of_hashes, operation_tuple), - ) + .callback(::callbacks(self).execute( + hash_of_hashes, + operation_hash, + operation, + output_payments, + )) .gas_for_callback(CALLBACK_GAS) .register_promise(); } } + + Ok(()) } fn execute_sc_call( &self, hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, - ) { - let transfer_data = operation_tuple - .operation - .data - .opt_transfer_data - .as_ref() - .unwrap(); + operation_hash: &ManagedBuffer, + operation: &Operation, + ) -> Result<(), ManagedBuffer> { + let transfer_data = match operation.data.opt_transfer_data.as_ref() { + Some(data) => data, + None => return Err(NOTHING_TO_TRANSFER.into()), + }; + + self.validate_transfer_data(transfer_data)?; + let args = ManagedArgBuffer::from(transfer_data.args.clone()); self.tx() - .to(&operation_tuple.operation.to) + .to(&operation.to) .raw_call(transfer_data.function.clone()) .arguments_raw(args) .gas(transfer_data.gas_limit) - .callback( - ::callbacks(self).execute(hash_of_hashes, operation_tuple), - ) + .callback(::callbacks(self).execute( + hash_of_hashes, + operation_hash, + operation, + &ManagedVec::new(), + )) .gas_for_callback(CALLBACK_GAS) .register_promise(); + + Ok(()) } #[promises_callback] fn execute( &self, hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, + operation_hash: &ManagedBuffer, + operation: &Operation, + output_payments: &ManagedVec>, #[call_result] result: ManagedAsyncCallResult, ) { match result { ManagedAsyncCallResult::Ok(_) => { - self.execute_bridge_operation_event(hash_of_hashes, &operation_tuple.op_hash); + self.complete_operation(hash_of_hashes, operation_hash, None); } - ManagedAsyncCallResult::Err(_) => { - self.emit_transfer_failed_events(hash_of_hashes, operation_tuple); + ManagedAsyncCallResult::Err(err) => { + let refund_result = self.refund_transfers(output_payments, operation); + let merged_err_message = self.merge_error_if_any(err.err_msg, refund_result); + + self.complete_operation(hash_of_hashes, operation_hash, Some(merged_err_message)); } } - - self.remove_executed_hash(hash_of_hashes, &operation_tuple.op_hash); } - fn emit_transfer_failed_events( + fn refund_transfers( &self, - hash_of_hashes: &ManagedBuffer, - operation_tuple: &OperationTuple, - ) { - self.execute_bridge_operation_event(hash_of_hashes, &operation_tuple.op_hash); - - if operation_tuple.operation.tokens.is_empty() { - return; + output_payments: &ManagedVec>, + operation: &Operation, + ) -> Result<(), ManagedBuffer> { + if output_payments.is_empty() { + return Ok(()); } - for operation_token in &operation_tuple.operation.tokens { - self.burn_failed_transfer_token(&operation_token); + let mut burn_errors = ManagedVec::new(); + + for i in 0..output_payments.len() { + match self.burn_failed_transfer_token(&output_payments.get(i), &operation.tokens.get(i)) + { + Ok(_) => { + if self.is_burn_mechanism_set(&output_payments.get(i)) { + self.deposited_tokens_amount(&output_payments.get(i).token_identifier) + .update(|amount| { + *amount += output_payments.get(i).token_data.amount.clone() + }); + } + } + Err(err_msg) => { + burn_errors.push(err_msg); + } + } } let sc_address = self.blockchain().get_sc_address(); - let tx_nonce = self.get_and_save_next_tx_id(); + let tx_nonce = self.get_current_and_increment_tx_nonce(); self.deposit_event( - &operation_tuple.operation.data.op_sender, - &operation_tuple - .operation - .map_tokens_to_multi_value_encoded(), + &operation.data.op_sender, + &operation.map_tokens_to_multi_value_encoded(), OperationData::new(tx_nonce, sc_address.clone(), None), ); + + if !burn_errors.is_empty() { + return Err(self.combine_error_messages(&burn_errors)); + } + Ok(()) } - fn burn_failed_transfer_token(&self, operation_token: &OperationEsdtPayment) { - let sov_to_mvx_mapper = - self.sovereign_to_multiversx_token_id_mapper(&operation_token.token_identifier); + fn burn_failed_transfer_token( + &self, + output_payment: &OperationEsdtPayment, + operation_token: &OperationEsdtPayment, + ) -> Result<(), ManagedBuffer> { + let mvx_to_sov_mapper = + self.multiversx_to_sovereign_token_id_mapper(&output_payment.token_identifier); + if mvx_to_sov_mapper.is_empty() && !self.is_native_token(&output_payment.token_identifier) { + return Ok(()); + } - if sov_to_mvx_mapper.is_empty() { - return; + let esdt_token_id = output_payment.token_identifier.clone().unwrap_esdt(); + self.try_esdt_local_burn( + &esdt_token_id, + output_payment.token_nonce, + &output_payment.token_data.amount, + )?; + + if self.is_nft(&operation_token.token_data.token_type) { + self.clear_mvx_to_sov_esdt_info_mapper( + &output_payment.token_identifier, + output_payment.token_nonce, + ); + self.clear_sov_to_mvx_esdt_info_mapper( + &operation_token.token_identifier, + operation_token.token_nonce, + ); } - let mvx_token_id = sov_to_mvx_mapper.get(); - let mut mvx_token_nonce = 0; - - if operation_token.token_nonce > 0 { - mvx_token_nonce = self - .sovereign_to_multiversx_esdt_info_mapper( - &operation_token.token_identifier, - operation_token.token_nonce, - ) - .get() - .token_nonce; - - if self.is_nft(&operation_token.token_data.token_type) { - self.clear_sov_to_mvx_esdt_info_mapper( - &operation_token.token_identifier, - operation_token.token_nonce, - ); - self.clear_mvx_to_sov_esdt_info_mapper(&mvx_token_id, mvx_token_nonce); + Ok(()) + } + + fn merge_error_if_any( + &self, + outer_error: ManagedBuffer, + result: Result<(), ManagedBuffer>, + ) -> ManagedBuffer { + match result { + Ok(()) => outer_error, + Err(refund_err) => { + let mut errors: ManagedVec = ManagedVec::new(); + errors.push(outer_error); + errors.push(refund_err); + self.combine_error_messages(&errors) } } - - self.tx() - .to(ToSelf) - .typed(system_proxy::UserBuiltinProxy) - .esdt_local_burn( - &mvx_token_id, - mvx_token_nonce, - &operation_token.token_data.amount, - ) - .sync_call(); } fn get_mvx_token_id( &self, operation_token: &OperationEsdtPayment, - ) -> Option> { - let sov_to_mvx_mapper = - self.sovereign_to_multiversx_token_id_mapper(&operation_token.token_identifier); + ) -> Result, ManagedBuffer> { + let token_identifier = &operation_token.token_identifier; + let registered_mapping = self.sovereign_to_multiversx_token_id_mapper(token_identifier); - if !sov_to_mvx_mapper.is_empty() { - return Some(sov_to_mvx_mapper.get()); + if !registered_mapping.is_empty() { + return Ok(Some(registered_mapping.get())); } - if self.is_native_token(&operation_token.token_identifier) { - Some(operation_token.token_identifier.clone()) - } else { - None + if self.has_prefix(token_identifier) { + return Err(TOKEN_NOT_REGISTERED.into()); } + + if self.is_native_token(token_identifier) { + return Ok(Some(token_identifier.clone())); + } + + Ok(None) } - fn get_mvx_nonce_from_mapper(self, token_id: &TokenIdentifier, nonce: u64) -> u64 { + fn get_mvx_nonce_from_mapper(&self, token_id: &EgldOrEsdtTokenIdentifier, nonce: u64) -> u64 { let esdt_info_mapper = self.sovereign_to_multiversx_esdt_info_mapper(token_id, nonce); if esdt_info_mapper.is_empty() { return 0; } esdt_info_mapper.get().token_nonce } + + #[inline] + fn is_burn_mechanism_set(&self, operation_token: &OperationEsdtPayment) -> bool { + self.is_fungible(&operation_token.token_data.token_type) + && self + .burn_mechanism_tokens() + .contains(&operation_token.token_identifier) + } + + fn validate_transfer_data( + &self, + transfer_data: &TransferData, + ) -> Result<(), ManagedBuffer> { + if transfer_data.gas_limit > MAX_GAS_PER_TRANSACTION { + return Err(GAS_LIMIT_TOO_HIGH.into()); + } + + Ok(()) + } } diff --git a/mvx-esdt-safe/src/lib.rs b/mvx-esdt-safe/src/lib.rs index 675968668..d4cdea264 100644 --- a/mvx-esdt-safe/src/lib.rs +++ b/mvx-esdt-safe/src/lib.rs @@ -1,7 +1,16 @@ #![no_std] +use error_messages::{ + ADDRESS_NOT_VALID_SC_ADDRESS, FEE_MARKET_NOT_SET, NATIVE_TOKEN_NOT_REGISTERED, + SETUP_PHASE_ALREADY_COMPLETED, SETUP_PHASE_NOT_COMPLETED, +}; + use multiversx_sc::imports::*; -use structs::configs::EsdtSafeConfig; +use multiversx_sc_modules::{only_admin, pause}; +use structs::{ + configs::{EsdtSafeConfig, PauseStatusOperation, UpdateEsdtSafeConfigOperation}, + generate_hash::GenerateHash, +}; pub mod bridging_mechanism; pub mod deposit; @@ -16,53 +25,146 @@ pub trait MvxEsdtSafe: + register_token::RegisterTokenModule + bridging_mechanism::BridgingMechanism + cross_chain::deposit_common::DepositCommonModule - + cross_chain::events::EventsModule + + custom_events::CustomEventsModule + cross_chain::storage::CrossChainStorage + cross_chain::execute_common::ExecuteCommonModule - + multiversx_sc_modules::pause::PauseModule - + utils::UtilsModule - + multiversx_sc_modules::only_admin::OnlyAdminModule + + pause::PauseModule + + common_utils::CommonUtilsModule + + setup_phase::SetupPhaseModule + + only_admin::OnlyAdminModule { #[init] fn init( &self, - header_verifier_address: ManagedAddress, + sovereign_owner: ManagedAddress, + sovereign_forge_address: ManagedAddress, + sov_token_prefix: ManagedBuffer, opt_config: OptionalValue>, ) { - self.require_sc_address(&header_verifier_address); - self.header_verifier_address().set(&header_verifier_address); - self.admins().insert(self.blockchain().get_caller()); - - self.esdt_safe_config().set( - opt_config - .into_option() - .inspect(|config| self.require_esdt_config_valid(config)) - .unwrap_or_else(EsdtSafeConfig::default_config), - ); + self.validate_chain_id(&sov_token_prefix); + self.sov_token_prefix().set(sov_token_prefix); + let new_config = self.resolve_esdt_safe_config(opt_config); + + self.add_admin(sovereign_owner); + require!( + self.blockchain() + .is_smart_contract(&sovereign_forge_address), + ADDRESS_NOT_VALID_SC_ADDRESS + ); + self.sovereign_forge_address().set(sovereign_forge_address); + self.esdt_safe_config().set(new_config); self.set_paused(true); } - #[only_admin] - #[endpoint(updateConfiguration)] - fn update_configuration(&self, new_config: EsdtSafeConfig) { - self.require_esdt_config_valid(&new_config); + #[upgrade] + fn upgrade(&self) {} + + #[only_owner] + #[endpoint(updateEsdtSafeConfigSetupPhase)] + fn update_esdt_safe_config_during_setup_phase(&self, new_config: EsdtSafeConfig) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + if let Some(error_message) = self.is_esdt_safe_config_valid(&new_config) { + sc_panic!(error_message); + } + self.esdt_safe_config().set(new_config); } - #[only_admin] + #[endpoint(updateEsdtSafeConfig)] + fn update_esdt_safe_config( + &self, + hash_of_hashes: ManagedBuffer, + update_config_operation: UpdateEsdtSafeConfigOperation, + ) { + let config_hash = update_config_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &config_hash, + update_config_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &config_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &config_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + if let Some(error_message) = + self.is_esdt_safe_config_valid(&update_config_operation.esdt_safe_config) + { + self.complete_operation( + &hash_of_hashes, + &config_hash, + Some(ManagedBuffer::from(error_message)), + ); + return; + } else { + self.esdt_safe_config() + .set(update_config_operation.esdt_safe_config); + self.complete_operation(&hash_of_hashes, &config_hash, None); + } + } + + #[endpoint(pauseContract)] + fn switch_pause_status( + &self, + hash_of_hashes: ManagedBuffer, + pause_status_operation: PauseStatusOperation, + ) { + let operation_hash = pause_status_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + pause_status_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + + self.set_paused(pause_status_operation.status); + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } + + #[only_owner] #[endpoint(setFeeMarketAddress)] fn set_fee_market_address(&self, fee_market_address: ManagedAddress) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); self.require_sc_address(&fee_market_address); self.fee_market_address().set(fee_market_address); } - #[only_admin] - #[endpoint(setMaxBridgedAmount)] - fn set_max_bridged_amount(&self, token_id: TokenIdentifier, max_amount: BigUint) { - self.max_bridged_amount(&token_id).set(&max_amount); - } + #[only_owner] + #[endpoint(completeSetupPhase)] + fn complete_setup_phase(&self) { + if self.is_setup_phase_complete() { + return; + } - #[upgrade] - fn upgrade(&self) {} + require!(!self.native_token().is_empty(), NATIVE_TOKEN_NOT_REGISTERED); + require!(!self.fee_market_address().is_empty(), FEE_MARKET_NOT_SET); + + self.unpause_endpoint(); + self.remove_admin(self.admins().get_by_index(1)); + self.setup_phase_complete().set(true); + } } diff --git a/mvx-esdt-safe/src/register_token.rs b/mvx-esdt-safe/src/register_token.rs index 722bce8fe..280058836 100644 --- a/mvx-esdt-safe/src/register_token.rs +++ b/mvx-esdt-safe/src/register_token.rs @@ -1,49 +1,115 @@ -use cross_chain::REGISTER_GAS; -use error_messages::{CANNOT_REGISTER_TOKEN, INVALID_TYPE, NATIVE_TOKEN_ALREADY_REGISTERED}; -use multiversx_sc::types::EsdtTokenType; -use structs::{EsdtInfo, IssueEsdtArgs}; +use cross_chain::{DEFAULT_ISSUE_COST, REGISTER_GAS}; +use error_messages::{ + ESDT_SAFE_STILL_PAUSED, INVALID_PREFIX_FOR_REGISTER, NATIVE_TOKEN_ALREADY_REGISTERED, + NOT_ENOUGH_EGLD_FOR_REGISTER, SETUP_PHASE_ALREADY_COMPLETED, TOKEN_ALREADY_REGISTERED, +}; +use multiversx_sc::{chain_core::EGLD_000000_TOKEN_IDENTIFIER, types::EsdtTokenType}; +use multiversx_sc_modules::only_admin; +use structs::{ + aliases::EventPaymentTuple, generate_hash::GenerateHash, EsdtInfo, RegisterTokenOperation, +}; multiversx_sc::imports!(); multiversx_sc::derive_imports!(); #[multiversx_sc::module] pub trait RegisterTokenModule: - utils::UtilsModule + common_utils::CommonUtilsModule + cross_chain::storage::CrossChainStorage + cross_chain::deposit_common::DepositCommonModule + cross_chain::execute_common::ExecuteCommonModule + + custom_events::CustomEventsModule + + multiversx_sc_modules::pause::PauseModule + + setup_phase::SetupPhaseModule + + only_admin::OnlyAdminModule { - #[payable("EGLD")] #[endpoint(registerToken)] - fn register_token( + fn register_sovereign_token( &self, - sov_token_id: TokenIdentifier, - token_type: EsdtTokenType, - token_display_name: ManagedBuffer, - token_ticker: ManagedBuffer, - num_decimals: usize, + hash_of_hashes: ManagedBuffer, + register_token_operation: RegisterTokenOperation, ) { - self.require_sov_token_id_not_registered(&sov_token_id); - - require!(self.has_prefix(&sov_token_id), CANNOT_REGISTER_TOKEN); - let issue_cost = self.call_value().egld().clone_value(); + let token_hash = register_token_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &token_hash, + register_token_operation.data.op_nonce, + ) { + self.complete_operation(&hash_of_hashes, &token_hash, Some(lock_operation_error)); + return; + } + if self.is_paused() { + self.deposit_event( + ®ister_token_operation.data.op_sender.clone(), + &self.create_issue_cost_event_payment_tuple(), + register_token_operation.data.clone(), + ); + self.complete_operation( + &hash_of_hashes, + &token_hash, + Some(ESDT_SAFE_STILL_PAUSED.into()), + ); + return; + } - match token_type { - EsdtTokenType::Invalid => sc_panic!(INVALID_TYPE), - _ => self.handle_token_issue(IssueEsdtArgs { - sov_token_id: sov_token_id.clone(), - issue_cost, - token_display_name, - token_ticker, - token_type, - num_decimals, - }), + if self + .blockchain() + .get_balance(&self.blockchain().get_sc_address()) + < DEFAULT_ISSUE_COST + { + self.deposit_event( + ®ister_token_operation.data.op_sender.clone(), + &self.create_issue_cost_event_payment_tuple(), + register_token_operation.data.clone(), + ); + self.complete_operation( + &hash_of_hashes, + &token_hash, + Some(NOT_ENOUGH_EGLD_FOR_REGISTER.into()), + ); + return; + } + if self.is_sov_token_id_registered(®ister_token_operation.token_id) { + self.deposit_event( + ®ister_token_operation.data.op_sender.clone(), + &self.create_issue_cost_event_payment_tuple(), + register_token_operation.data.clone(), + ); + self.complete_operation( + &hash_of_hashes, + &token_hash, + Some(TOKEN_ALREADY_REGISTERED.into()), + ); + return; } + if !self.has_sov_prefix( + ®ister_token_operation.token_id, + &self.sov_token_prefix().get(), + ) { + self.deposit_event( + ®ister_token_operation.data.op_sender.clone(), + &self.create_issue_cost_event_payment_tuple(), + register_token_operation.data.clone(), + ); + self.complete_operation( + &hash_of_hashes, + &token_hash, + Some(INVALID_PREFIX_FOR_REGISTER.into()), + ); + return; + } + + self.handle_token_issue(register_token_operation, hash_of_hashes, token_hash); } #[payable("EGLD")] - #[only_owner] + #[only_admin] #[endpoint(registerNativeToken)] - fn register_native_token(&self, token_ticker: ManagedBuffer, token_name: ManagedBuffer) { + fn register_native_token(&self, ticker: ManagedBuffer, name: ManagedBuffer) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + require!( self.native_token().is_empty(), NATIVE_TOKEN_ALREADY_REGISTERED @@ -54,8 +120,8 @@ pub trait RegisterTokenModule: .typed(ESDTSystemSCProxy) .issue_and_set_all_roles( self.call_value().egld().clone_value(), - token_name, - &token_ticker, + name, + ticker, EsdtTokenType::Fungible, 18, ) @@ -64,34 +130,56 @@ pub trait RegisterTokenModule: .register_promise(); } - fn handle_token_issue(&self, args: IssueEsdtArgs) { + fn handle_token_issue( + &self, + args: RegisterTokenOperation, + hash_of_hashes: ManagedBuffer, + token_hash: ManagedBuffer, + ) { + let token_display_name = args.token_display_name.clone(); + let token_ticker = args.token_ticker.clone(); + let token_type = args.token_type; + let num_decimals = args.num_decimals; + self.tx() .to(ESDTSystemSCAddress) .typed(ESDTSystemSCProxy) .issue_and_set_all_roles( - args.issue_cost, - args.token_display_name, - args.token_ticker, - args.token_type, - args.num_decimals, + BigUint::from(DEFAULT_ISSUE_COST), + token_display_name, + token_ticker, + token_type, + num_decimals, ) .gas(REGISTER_GAS) - .callback(self.callbacks().issue_callback(&args.sov_token_id)) + .callback( + self.callbacks() + .register_token(&args, hash_of_hashes, token_hash), + ) .register_promise(); } #[promises_callback] - fn issue_callback( + fn register_token( &self, - sov_token_id: &TokenIdentifier, - #[call_result] result: ManagedAsyncCallResult>, + token_to_register: &RegisterTokenOperation, + hash_of_hashes: ManagedBuffer, + token_hash: ManagedBuffer, + #[call_result] result: ManagedAsyncCallResult>, ) { match result { ManagedAsyncCallResult::Ok(mvx_token_id) => { - self.set_corresponding_token_ids(sov_token_id, &mvx_token_id); + self.set_corresponding_token_ids(&token_to_register.token_id, &mvx_token_id); + self.complete_operation(&hash_of_hashes, &token_hash, None); } ManagedAsyncCallResult::Err(error) => { - sc_panic!("There was an error at issuing token: '{}'", error.err_msg); + let tokens = self.create_issue_cost_event_payment_tuple(); + self.deposit_event( + &token_to_register.data.op_sender.clone(), + &tokens, + token_to_register.data.clone(), + ); + self.complete_operation(&hash_of_hashes, &token_hash, Some(error.err_msg)); } } } @@ -99,7 +187,7 @@ pub trait RegisterTokenModule: #[promises_callback] fn native_token_issue_callback( &self, - #[call_result] result: ManagedAsyncCallResult>, + #[call_result] result: ManagedAsyncCallResult>, ) { match result { ManagedAsyncCallResult::Ok(native_token_id) => { @@ -115,8 +203,8 @@ pub trait RegisterTokenModule: } fn set_corresponding_token_ids( &self, - sov_token_id: &TokenIdentifier, - mvx_token_id: &TokenIdentifier, + sov_token_id: &EgldOrEsdtTokenIdentifier, + mvx_token_id: &EgldOrEsdtTokenIdentifier, ) { self.sovereign_to_multiversx_token_id_mapper(sov_token_id) .set(mvx_token_id); @@ -127,9 +215,9 @@ pub trait RegisterTokenModule: fn update_esdt_info_mappers( &self, - sov_id: &TokenIdentifier, + sov_id: &EgldOrEsdtTokenIdentifier, sov_nonce: u64, - mvx_id: &TokenIdentifier, + mvx_id: &EgldOrEsdtTokenIdentifier, new_nft_nonce: u64, ) { self.sovereign_to_multiversx_esdt_info_mapper(sov_id, sov_nonce) @@ -144,4 +232,18 @@ pub trait RegisterTokenModule: token_nonce: sov_nonce, }); } + + #[allow(clippy::field_reassign_with_default)] + fn create_issue_cost_event_payment_tuple( + &self, + ) -> MultiValueEncoded> { + let mut token_data = EsdtTokenData::default(); + token_data.amount = DEFAULT_ISSUE_COST.into(); + + MultiValueEncoded::from_iter([MultiValue3(( + EGLD_000000_TOKEN_IDENTIFIER.into(), + 0u64, + token_data, + ))]) + } } diff --git a/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_setup.rs b/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_setup.rs index 731c09377..45b1c7849 100644 --- a/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_setup.rs +++ b/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_setup.rs @@ -1,52 +1,59 @@ +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::base_setup::init::{AccountSetup, BaseSetup}; +use common_test_setup::base_setup::log_validations::assert_expected_logs; use common_test_setup::constants::{ - ESDT_SAFE_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, HEADER_VERIFIER_ADDRESS, - MVX_ESDT_SAFE_CODE_PATH, ONE_HUNDRED_MILLION, OWNER_ADDRESS, OWNER_BALANCE, SECOND_TEST_TOKEN, - USER, + COMPLETE_SETUP_PHASE_ENDPOINT, DEPOSIT_EVENT, ESDT_SAFE_ADDRESS, EXECUTED_BRIDGE_OP_EVENT, + FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, FIRST_TOKEN_ID, HEADER_VERIFIER_ADDRESS, + MVX_ESDT_SAFE_CODE_PATH, NATIVE_TEST_TOKEN, ONE_HUNDRED_MILLION, OWNER_ADDRESS, OWNER_BALANCE, + SC_CALL_EVENT, SECOND_TEST_TOKEN, SECOND_TOKEN_ID, SOVEREIGN_FORGE_SC_ADDRESS, + SOVEREIGN_TOKEN_PREFIX, TRUSTED_TOKEN, UNPAUSE_CONTRACT_LOG, UPDATE_ESDT_SAFE_CONFIG_ENDPOINT, + USER_ADDRESS, }; -use common_test_setup::{AccountSetup, BaseSetup, RegisterTokenArgs}; +use common_test_setup::log; +use cross_chain::storage::CrossChainStorage; +use multiversx_sc::types::ReturnsHandledOrError; use multiversx_sc::{ - codec::TopEncode, imports::OptionalValue, types::{ - BigUint, EsdtLocalRole, ManagedAddress, ManagedBuffer, ManagedVec, MultiValueEncoded, - TestSCAddress, TestTokenIdentifier, TokenIdentifier, + BigUint, EsdtLocalRole, EsdtTokenIdentifier, ManagedAddress, ManagedBuffer, TestSCAddress, + TestTokenIdentifier, }, }; -use multiversx_sc_modules::transfer_role_proxy::PaymentsVec; -use multiversx_sc_scenario::{ - api::StaticApi, multiversx_chain_vm::crypto_functions::sha256, ReturnsHandledOrError, - ReturnsLogs, ScenarioTxRun, ScenarioTxWhitebox, +use multiversx_sc_scenario::imports::*; +use mvx_esdt_safe::MvxEsdtSafe; +use proxies::mvx_esdt_safe_proxy::MvxEsdtSafeProxy; +use structs::configs::{ + PauseStatusOperation, SetBurnMechanismOperation, SetLockMechanismOperation, SovereignConfig, + UpdateEsdtSafeConfigOperation, }; -use mvx_esdt_safe::{bridging_mechanism::TRUSTED_TOKEN_IDS, MvxEsdtSafe}; -use proxies::{header_verifier_proxy::HeaderverifierProxy, mvx_esdt_safe_proxy::MvxEsdtSafeProxy}; +use structs::forge::ScArray; +use structs::OperationHashStatus; use structs::{ - aliases::OptionalValueTransferDataTuple, configs::EsdtSafeConfig, operation::Operation, + aliases::{OptionalValueTransferDataTuple, PaymentsVec}, + configs::EsdtSafeConfig, + fee::FeeStruct, + operation::Operation, + RegisterTokenOperation, }; pub struct MvxEsdtSafeTestState { pub common_setup: BaseSetup, + pub fees_enabled: bool, } impl MvxEsdtSafeTestState { #[allow(clippy::new_without_default)] pub fn new() -> Self { let owner_account = AccountSetup { - address: OWNER_ADDRESS, + address: OWNER_ADDRESS.to_address(), + code_path: None, esdt_balances: Some(vec![ + (FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), + (SECOND_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), + (FEE_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), ( - TestTokenIdentifier::new(FIRST_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), - ( - TestTokenIdentifier::new(SECOND_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), - ( - TestTokenIdentifier::new(FEE_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), - ( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, ONE_HUNDRED_MILLION.into(), ), ]), @@ -54,53 +61,26 @@ impl MvxEsdtSafeTestState { }; let user_account = AccountSetup { - address: USER, - esdt_balances: Some(vec![( - TestTokenIdentifier::new(FIRST_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - )]), + address: USER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into())]), egld_balance: Some(OWNER_BALANCE.into()), }; let account_setups = vec![owner_account, user_account]; - let mut common_setup = BaseSetup::new(account_setups); + let common_setup = BaseSetup::new(account_setups); - common_setup - .world - .register_contract(MVX_ESDT_SAFE_CODE_PATH, mvx_esdt_safe::ContractBuilder); - - Self { common_setup } + Self { + common_setup, + fees_enabled: false, + } } - pub fn deploy_contract( - &mut self, - header_verifier_address: TestSCAddress, - opt_config: OptionalValue>, - ) -> &mut Self { + pub fn deploy_contract_with_roles(&mut self, fee: Option>) -> &mut Self { + self.fees_enabled = fee.is_some(); self.common_setup - .world - .tx() - .from(OWNER_ADDRESS) - .typed(MvxEsdtSafeProxy) - .init(header_verifier_address, opt_config) - .code(MVX_ESDT_SAFE_CODE_PATH) - .new_address(ESDT_SAFE_ADDRESS) - .run(); - - self.common_setup - .world - .tx() - .from(OWNER_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .typed(MvxEsdtSafeProxy) - .unpause_endpoint() - .run(); - - self - } - - pub fn deploy_contract_with_roles(&mut self) -> &mut Self { + .deploy_sovereign_forge(OptionalValue::None); self.common_setup .world .account(ESDT_SAFE_ADDRESS) @@ -108,7 +88,7 @@ impl MvxEsdtSafeTestState { .code(MVX_ESDT_SAFE_CODE_PATH) .owner(OWNER_ADDRESS) .esdt_roles( - TokenIdentifier::from(FIRST_TEST_TOKEN), + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), @@ -116,7 +96,7 @@ impl MvxEsdtSafeTestState { ], ) .esdt_roles( - TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]), + EsdtTokenIdentifier::from(TRUSTED_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), @@ -124,35 +104,55 @@ impl MvxEsdtSafeTestState { ], ) .esdt_roles( - TokenIdentifier::from(SECOND_TEST_TOKEN), + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), ], ) .esdt_roles( - TokenIdentifier::from(FEE_TOKEN), + EsdtTokenIdentifier::from(FEE_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), ], + ) + .esdt_roles( + EsdtTokenIdentifier::from(NATIVE_TEST_TOKEN), + vec![ + EsdtLocalRole::Burn.name().to_string(), + EsdtLocalRole::Mint.name().to_string(), + ], + ) + .esdt_roles( + EsdtTokenIdentifier::from(FIRST_TOKEN_ID), + vec![ + EsdtLocalRole::Burn.name().to_string(), + EsdtLocalRole::Mint.name().to_string(), + ], + ) + .esdt_roles( + EsdtTokenIdentifier::from(SECOND_TOKEN_ID), + vec![ + EsdtLocalRole::Burn.name().to_string(), + EsdtLocalRole::Mint.name().to_string(), + ], ); + self.common_setup.register_trusted_token(TRUSTED_TOKEN); + self.common_setup .world .tx() .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .whitebox(mvx_esdt_safe::contract_obj, |sc| { - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); + let config = EsdtSafeConfig::default_config(); sc.init( - HEADER_VERIFIER_ADDRESS.to_managed_address(), + OWNER_ADDRESS.to_managed_address(), + SOVEREIGN_FORGE_SC_ADDRESS.to_managed_address(), + SOVEREIGN_TOKEN_PREFIX.into(), OptionalValue::Some(config), ); }); @@ -166,83 +166,174 @@ impl MvxEsdtSafeTestState { .unpause_endpoint() .run(); + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(NATIVE_TEST_TOKEN)); + }); + + self.common_setup.deploy_fee_market(fee, ESDT_SAFE_ADDRESS); + self.set_fee_market_address(FEE_MARKET_ADDRESS); + self } - pub fn update_configuration( + pub fn update_esdt_safe_config_during_setup_phase( &mut self, new_config: EsdtSafeConfig, - err_message: Option<&str>, + expected_error_message: Option<&str>, ) { - let response = self + let result = self .common_setup .world .tx() .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(MvxEsdtSafeProxy) - .update_configuration(new_config) + .update_esdt_safe_config_during_setup_phase(new_config) .returns(ReturnsHandledOrError::new()) .run(); self.common_setup - .assert_expected_error_message(response, err_message); + .assert_expected_error_message(result, expected_error_message); + } + + pub fn switch_pause_status( + &mut self, + hash_of_hashes: &ManagedBuffer, + operation: PauseStatusOperation, + expected_logs: Vec, + ) { + let logs = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .switch_pause_status(hash_of_hashes, operation) + .returns(ReturnsLogs) + .run(); + + assert_expected_logs(logs, expected_logs); + } + + pub fn update_esdt_safe_config( + &mut self, + hash_of_hashes: &ManagedBuffer, + update_config_operation: UpdateEsdtSafeConfigOperation, + expected_error_message: Option<&str>, + ) { + let (result, logs) = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .update_esdt_safe_config(hash_of_hashes, update_config_operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.common_setup + .assert_expected_error_message(result, None); + + let expected_logs = vec![ + log!(UPDATE_ESDT_SAFE_CONFIG_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_error_message), + ]; + + assert_expected_logs(logs, expected_logs); } pub fn set_token_burn_mechanism( + &mut self, + hash_of_hashes: &ManagedBuffer, + set_burn_mechanism_operation: SetBurnMechanismOperation, + ) -> &mut Self { + let result = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .set_token_burn_mechanism(hash_of_hashes, set_burn_mechanism_operation) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(result, None); + + self + } + + pub fn set_token_burn_mechanism_before_setup_phase( &mut self, token_id: &str, expected_error_message: Option<&str>, ) -> &mut Self { - let response = self + let result = self .common_setup .world .tx() .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(MvxEsdtSafeProxy) - .set_token_burn_mechanism(TokenIdentifier::from(token_id)) + .set_token_burn_mechanism_setup_phase(EgldOrEsdtTokenIdentifier::esdt(token_id)) .returns(ReturnsHandledOrError::new()) .run(); - match response { - Ok(_) => assert!( - expected_error_message.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => { - assert_eq!(expected_error_message, Some(error.message.as_str())) - } - } + self.common_setup + .assert_expected_error_message(result, expected_error_message); self } - pub fn set_token_lock_mechanism( + pub fn set_token_lock_mechanism_before_setup_phase( &mut self, token_id: &str, expected_error_message: Option<&str>, ) -> &mut Self { - let response = self + let result = self .common_setup .world .tx() .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(MvxEsdtSafeProxy) - .set_token_lock_mechanism(TokenIdentifier::from(token_id)) + .set_token_lock_mechanism_setup_phase(EgldOrEsdtTokenIdentifier::esdt(token_id)) .returns(ReturnsHandledOrError::new()) .run(); - match response { - Ok(_) => assert!( - expected_error_message.is_none(), - "Transaction was successful, but expected error" - ), - Err(error) => { - assert_eq!(expected_error_message, Some(error.message.as_str())) - } - } + self.common_setup + .assert_expected_error_message(result, expected_error_message); + + self + } + + pub fn set_token_lock_mechanism( + &mut self, + hash_of_hashes: &ManagedBuffer, + set_lock_mechanism_operation: SetLockMechanismOperation, + ) -> &mut Self { + let result = self + .common_setup + .world + .tx() + .from(HEADER_VERIFIER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .set_token_lock_mechanism(hash_of_hashes, set_lock_mechanism_operation) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(result, None); self } @@ -258,15 +349,23 @@ impl MvxEsdtSafeTestState { .run(); } + fn is_fee_or_no_payment( + &self, + opt_transfer_data: &OptionalValueTransferDataTuple, + payment: &PaymentsVec, + ) -> bool { + opt_transfer_data.is_some() + && (payment.is_empty() || (payment.len() == 1 && self.fees_enabled)) + } + pub fn deposit( &mut self, to: ManagedAddress, opt_transfer_data: OptionalValueTransferDataTuple, payment: PaymentsVec, expected_error_message: Option<&str>, - expected_custom_log: Option<&str>, ) { - let (logs, response) = self + let (logs, result) = self .common_setup .world .tx() @@ -280,39 +379,41 @@ impl MvxEsdtSafeTestState { .run(); self.common_setup - .assert_expected_error_message(response, expected_error_message); - - if let Some(custom_log) = expected_custom_log { - self.common_setup.assert_expected_log(logs, custom_log) - }; + .assert_expected_error_message(result, expected_error_message); + + if expected_error_message.is_none() { + let expected_logs = if self.is_fee_or_no_payment(&opt_transfer_data, &payment) { + vec![log!(DEPOSIT_EVENT, topics: [SC_CALL_EVENT])] + } else { + vec![log!(DEPOSIT_EVENT, topics: [DEPOSIT_EVENT])] + }; + assert_expected_logs(logs, expected_logs); + } } pub fn register_token( &mut self, - register_token_args: RegisterTokenArgs, - payment: BigUint, + register_token_args: RegisterTokenOperation, + hash_of_hashes: ManagedBuffer, expected_error_message: Option<&str>, + expected_logs: Vec, ) { - let response = self + let (result, logs) = self .common_setup .world .tx() .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(MvxEsdtSafeProxy) - .register_token( - register_token_args.sov_token_id, - register_token_args.token_type, - ManagedBuffer::from(register_token_args.token_display_name), - ManagedBuffer::from(register_token_args.token_ticker), - register_token_args.num_decimals, - ) - .egld(payment) + .register_sovereign_token(&hash_of_hashes, register_token_args) .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) .run(); self.common_setup - .assert_expected_error_message(response, expected_error_message); + .assert_expected_error_message(result, expected_error_message); + + assert_expected_logs(logs, expected_logs); } pub fn register_native_token( @@ -322,7 +423,7 @@ impl MvxEsdtSafeTestState { payment: BigUint, expected_error_message: Option<&str>, ) { - let response = self + let result = self .common_setup .world .tx() @@ -338,17 +439,54 @@ impl MvxEsdtSafeTestState { .run(); self.common_setup - .assert_expected_error_message(response, expected_error_message); + .assert_expected_error_message(result, expected_error_message); + } + + pub fn register_and_execute_operation( + &mut self, + operation: &Operation, + hash_of_hashes: &ManagedBuffer, + signature: ManagedBuffer, + num_validators: usize, + expected_logs: Vec, + check_hash_status: bool, + ) { + let epoch = 0; + let operation_hash = self.common_setup.get_operation_hash(operation); + let bitmap = self.common_setup.full_bitmap(num_validators as u64); + + let operations_hashes = + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + self.common_setup.register_operation( + OWNER_ADDRESS, + signature, + hash_of_hashes, + bitmap, + epoch, + operations_hashes, + ); + + if check_hash_status { + self.common_setup + .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); + } + + self.execute_operation(hash_of_hashes, operation, expected_logs); + + if check_hash_status { + self.common_setup + .check_operation_hash_status_is_empty(&operation_hash); + } } pub fn execute_operation( &mut self, hash_of_hashes: &ManagedBuffer, operation: &Operation, - expected_error_message: Option<&str>, - expected_custom_log: Option<&str>, + expected_logs: Vec, ) { - let (logs, response) = self + let (logs, result) = self .common_setup .world .tx() @@ -361,54 +499,74 @@ impl MvxEsdtSafeTestState { .run(); self.common_setup - .assert_expected_error_message(response, expected_error_message); + .assert_expected_error_message(result, None); - if let Some(custom_log) = expected_custom_log { - self.common_setup.assert_expected_log(logs, custom_log) - }; + assert_expected_logs(logs, expected_logs); } - pub fn set_esdt_safe_address_in_header_verifier(&mut self, esdt_safe_address: TestSCAddress) { - self.common_setup + pub fn complete_setup_phase(&mut self) { + let (logs, result) = self + .common_setup .world .tx() .from(OWNER_ADDRESS) - .to(HEADER_VERIFIER_ADDRESS) - .typed(HeaderverifierProxy) - .set_esdt_safe_address(esdt_safe_address) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() + .returns(ReturnsLogs) + .returns(ReturnsHandledOrError::new()) .run(); - } - pub fn register_operation( - &mut self, - signature: ManagedBuffer, - hash_of_hashes: &ManagedBuffer, - operations_hashes: MultiValueEncoded>, - ) { self.common_setup + .assert_expected_error_message(result, None); + + let expected_logs = + vec![log!(COMPLETE_SETUP_PHASE_ENDPOINT, topics: [UNPAUSE_CONTRACT_LOG])]; + + assert_expected_logs(logs, expected_logs); + + self.common_setup + .change_ownership_to_header_verifier(ESDT_SAFE_ADDRESS); + } + + pub fn complete_setup_phase_as_header_verifier(&mut self) { + let result = self + .common_setup .world .tx() - .from(OWNER_ADDRESS) - .to(HEADER_VERIFIER_ADDRESS) - .typed(HeaderverifierProxy) - .register_bridge_operations( - signature, - hash_of_hashes, - ManagedBuffer::new(), - ManagedBuffer::new(), - operations_hashes, - ) + .from(HEADER_VERIFIER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) .run(); + + self.common_setup + .assert_expected_error_message(result, None); } - pub fn get_operation_hash( + pub fn deploy_and_complete_setup_phase( &mut self, - operation: &Operation, + hash_of_hashes: &ManagedBuffer, ) -> ManagedBuffer { - let mut serialized_operation: ManagedBuffer = ManagedBuffer::new(); - let _ = operation.top_encode(&mut serialized_operation); - let sha256 = sha256(&serialized_operation.to_vec()); + self.deploy_contract_with_roles(None); + self.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + let (signature, public_keys) = self.common_setup.get_sig_and_pub_keys(1, hash_of_hashes); + self.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + self.common_setup.complete_chain_config_setup_phase(); + + self.common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + self.common_setup.complete_header_verifier_setup_phase(None); + self.complete_setup_phase(); - ManagedBuffer::new_from_bytes(&sha256) + signature } } diff --git a/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_tests.rs b/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_tests.rs new file mode 100644 index 000000000..251c1750a --- /dev/null +++ b/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_tests.rs @@ -0,0 +1,3545 @@ +use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::constants::{ + CROWD_TOKEN_ID, DEPOSIT_EVENT, ESDT_SAFE_ADDRESS, EXECUTED_BRIDGE_OP_EVENT, + EXECUTE_BRIDGE_OPS_ENDPOINT, EXECUTE_OPERATION_ENDPOINT, FEE_MARKET_ADDRESS, FEE_TOKEN, + FIRST_TEST_TOKEN, FIRST_TOKEN_ID, HEADER_VERIFIER_ADDRESS, ISSUE_COST, NATIVE_TEST_TOKEN, + ONE_HUNDRED_MILLION, ONE_HUNDRED_THOUSAND, ONE_HUNDRED_TOKENS, OWNER_ADDRESS, + PAUSE_CONTRACT_LOG, PER_GAS, PER_TRANSFER, REGISTER_TOKEN_ENDPOINT, REGISTER_TOKEN_EVENT, + SECOND_TEST_TOKEN, SECOND_TOKEN_ID, SOVEREIGN_RECEIVER_ADDRESS, SOV_FIRST_TOKEN_ID, + SOV_SECOND_TOKEN_ID, SOV_TOKEN, TESTING_SC_ADDRESS, TESTING_SC_ENDPOINT, TRUSTED_TOKEN, + USER_ADDRESS, WRONG_ENDPOINT_NAME, +}; +use common_test_setup::log; +use cross_chain::storage::CrossChainStorage; +use cross_chain::{DEFAULT_ISSUE_COST, MAX_GAS_PER_TRANSACTION}; +use error_messages::{ + BANNED_ENDPOINT_NAME, CALLER_IS_BLACKLISTED, CALLER_NOT_FROM_CURRENT_SOVEREIGN, + CURRENT_OPERATION_NOT_REGISTERED, DEPOSIT_AMOUNT_NOT_ENOUGH, DEPOSIT_OVER_MAX_AMOUNT, + ERR_EMPTY_PAYMENTS, ESDT_SAFE_STILL_PAUSED, GAS_LIMIT_TOO_HIGH, INVALID_FUNCTION_NOT_FOUND, + INVALID_PREFIX_FOR_REGISTER, INVALID_TYPE, MAX_GAS_LIMIT_PER_TX_EXCEEDED, + MINT_AND_BURN_ROLES_NOT_FOUND, NATIVE_TOKEN_ALREADY_REGISTERED, NATIVE_TOKEN_NOT_REGISTERED, + NOTHING_TO_TRANSFER, NOT_ENOUGH_EGLD_FOR_REGISTER, PAYMENT_DOES_NOT_COVER_FEE, + SETUP_PHASE_NOT_COMPLETED, TOKEN_ID_IS_NOT_TRUSTED, TOO_MANY_TOKENS, +}; +use header_verifier::storage::HeaderVerifierStorageModule; +use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; +use multiversx_sc::types::{ + EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, MultiEgldOrEsdtPayment, MultiValueEncoded, + ReturnsHandledOrError, +}; +use multiversx_sc::{ + imports::{MultiValue3, OptionalValue}, + types::{ + BigUint, EsdtTokenData, EsdtTokenIdentifier, EsdtTokenPayment, EsdtTokenType, + ManagedBuffer, ManagedVec, TestTokenIdentifier, + }, +}; +use multiversx_sc_modules::pause::PauseModule; +use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use multiversx_sc_scenario::ScenarioTxRun; +use multiversx_sc_scenario::{api::StaticApi, ScenarioTxWhitebox}; +use mvx_esdt_safe::bridging_mechanism::BridgingMechanism; +use mvx_esdt_safe_blackbox_setup::MvxEsdtSafeTestState; +use proxies::mvx_esdt_safe_proxy::MvxEsdtSafeProxy; +use setup_phase::SetupPhaseModule; +use structs::configs::{ + MaxBridgedAmount, PauseStatusOperation, SetBurnMechanismOperation, SetLockMechanismOperation, + SovereignConfig, UpdateEsdtSafeConfigOperation, +}; +use structs::fee::{FeeStruct, FeeType}; +use structs::forge::ScArray; +use structs::generate_hash::GenerateHash; +use structs::operation::TransferData; +use structs::{ + aliases::PaymentsVec, + configs::EsdtSafeConfig, + operation::{Operation, OperationData, OperationEsdtPayment}, +}; +use structs::{OperationHashStatus, RegisterTokenOperation}; +mod mvx_esdt_safe_blackbox_setup; + +/// ### TEST +/// M-ESDT_DEPLOY_OK +/// +/// ### ACTION +/// Call 'deploy_mvx_esdt_safe()' with default config +/// +/// ### EXPECTED +/// Contract is deployed with the default config +#[test] +fn test_deploy() { + let mut state = MvxEsdtSafeTestState::new(); + state.common_setup.deploy_mvx_esdt_safe(OptionalValue::None); +} + +/// ### TEST +/// M-ESDT_DEPLOY_FAIL +/// +/// ### ACTION +/// Call 'update_configuration()' with invalid config +/// +/// ### EXPECTED +/// Error MAX_GAS_LIMIT_PER_TX_EXCEEDED +#[test] +fn test_update_invalid_config() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + + let config = EsdtSafeConfig { + max_tx_gas_limit: MAX_GAS_PER_TRANSACTION + 1, + ..EsdtSafeConfig::default_config() + }; + + state.update_esdt_safe_config_during_setup_phase(config, Some(MAX_GAS_LIMIT_PER_TX_EXCEEDED)); +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with invalid token type +/// +/// ### EXPECTED +/// Error CANNOT_REGISTER_TOKEN +#[test] +#[ignore = "needs blackbox callback fix"] +fn test_register_token_invalid_type() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = format!("sov-{}", FIRST_TEST_TOKEN.as_str()); + let token_type = EsdtTokenType::Invalid; + let token_display_name = "TokenOne"; + let num_decimals = 3; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id.as_str()), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![ + log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT]), + log!(REGISTER_TOKEN_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_TYPE)), + ]; + state.register_token( + register_token_args, + hash_of_hashes, + Some(INVALID_TYPE), + expected_logs, + ); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(SECOND_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with invalid token type and prefix +/// +/// ### EXPECTED +/// Error INVALID_TYPE +#[test] +#[ignore = "needs blackbox callback fix"] +fn test_register_token_invalid_type_with_prefix() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = SOV_TOKEN; + let token_type = EsdtTokenType::Invalid; + let token_display_name = "TokenOne"; + let num_decimals = 3; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![ + log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT]), + log!(REGISTER_TOKEN_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_TYPE)), + ]; + state.register_token( + register_token_args, + hash_of_hashes, + Some(INVALID_TYPE), + expected_logs, + ); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(SECOND_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with token id not starting with prefix and not enough egld in balance +/// +/// ### EXPECTED +/// Error CANNOT_REGISTER_TOKEN +#[test] +fn test_register_token_not_enough_egld() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = SECOND_TEST_TOKEN; + let token_type = EsdtTokenType::Fungible; + let token_display_name = "TokenOne"; + let num_decimals = 3; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![ + log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT]), + log!(REGISTER_TOKEN_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(NOT_ENOUGH_EGLD_FOR_REGISTER)), + ]; + state.register_token(register_token_args, hash_of_hashes, None, expected_logs); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(SECOND_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_REG_OK +/// +/// ### ACTION +/// Call 'register_token()' with valid token id and type +/// +/// ### EXPECTED +/// The token is registered +#[test] +fn test_register_token_fungible_token() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = SOV_TOKEN; + let token_type = EsdtTokenType::Fungible; + let token_display_name = "TokenOne"; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + let num_decimals = 3; + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + + let epoch = 0; + + let payment = EgldOrEsdtTokenPayment::egld_payment(ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash.clone()]), + ); + + let expected_logs = vec![log!(REGISTER_TOKEN_EVENT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + state.register_token(register_token_args, hash_of_hashes, None, expected_logs); + + // TODO: add check for storage after callback fix +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with token id not starting with prefix and token type NonFungible +/// +/// ### EXPECTED +/// Error CANNOT_REGISTER_TOKEN +#[test] +fn test_register_token_nonfungible_token() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = FIRST_TEST_TOKEN; + let token_type = EsdtTokenType::NonFungible; + let token_display_name = "TokenOne"; + let num_decimals = 0; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![ + log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT]), + log!(REGISTER_TOKEN_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_PREFIX_FOR_REGISTER)), + ]; + state.register_token(register_token_args, hash_of_hashes, None, expected_logs); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(SECOND_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with empty payments_vec and no transfer_data +/// +/// ### EXPECTED +/// Error NOTHING_TO_TRANSFER +#[test] +fn test_deposit_nothing_to_transfer() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::new(), + Some(NOTHING_TO_TRANSFER), + ); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_SETUP_OK +/// +/// ### ACTION +/// Call 'complete_setup_phase()' +/// +/// ### EXPECTED +/// The setup phase is marked as completed in the smart contract's storage +#[test] +fn test_complete_setup_phase() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc.is_setup_phase_complete()); + }); +} + +/// ### TEST +/// M-ESDT_SETUP_FAIL +/// +/// ### ACTION +/// Call 'complete_setup_phase()' twice +/// +/// ### EXPECTED +/// Error SETUP_PHASE_ALREADY_COMPLETED +#[test] +fn test_complete_setup_phase_already_completed() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.complete_setup_phase(); + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc.is_setup_phase_complete()); + }); + + state.complete_setup_phase_as_header_verifier(); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with too many tokens in payments_vec +/// +/// ### EXPECTED +/// Error TOO_MANY_TOKENS +#[test] +fn test_deposit_too_many_tokens() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let esdt_token_payment = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::default(), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment; 11]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec, + Some(TOO_MANY_TOKENS), + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::zero(), + ); +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with valid payments_vec and no transfer_data +/// +/// ### EXPECTED +/// USER's balance is updated +#[test] +fn test_deposit_no_transfer_data() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec, + None, + ); + + let owner_tokens_vec = vec![ + MultiValue3::from(( + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(ONE_HUNDRED_MILLION - ONE_HUNDRED_THOUSAND), + )), + MultiValue3::from(( + SECOND_TEST_TOKEN, + 0u64, + BigUint::from(ONE_HUNDRED_MILLION - ONE_HUNDRED_THOUSAND), + )), + ]; + + state + .common_setup + .check_account_multiple_esdts(OWNER_ADDRESS.to_address(), owner_tokens_vec); + + let tokens_vec = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::from(ONE_HUNDRED_THOUSAND))), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::from(ONE_HUNDRED_THOUSAND))), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), tokens_vec); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with gas limit too high in transfer_data +/// +/// ### EXPECTED +/// Error GAS_LIMIT_TOO_HIGH +#[test] +fn test_deposit_gas_limit_too_high() { + let mut state = MvxEsdtSafeTestState::new(); + + let config = EsdtSafeConfig { + max_tx_gas_limit: 1, + ..EsdtSafeConfig::default_config() + }; + state + .common_setup + .deploy_mvx_esdt_safe(OptionalValue::Some(config)); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN)); + }); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.complete_setup_phase(); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec, + Some(GAS_LIMIT_TOO_HIGH), + ); + + let tokens_vec = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), tokens_vec); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with max bridged amount exceeded +/// +/// ### EXPECTED +/// Error DEPOSIT_OVER_MAX_AMOUNT +#[test] +fn test_deposit_max_bridged_amount_exceeded() { + let mut state = MvxEsdtSafeTestState::new(); + + let config = EsdtSafeConfig { + max_bridged_token_amounts: ManagedVec::from(vec![MaxBridgedAmount { + token_id: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + amount: BigUint::default(), + }]), + ..EsdtSafeConfig::default_config() + }; + + state + .common_setup + .deploy_mvx_esdt_safe(OptionalValue::Some(config)); + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN)); + }); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + state.complete_setup_phase(); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec, + Some(DEPOSIT_OVER_MAX_AMOUNT), + ); + + let tokens_vec = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), tokens_vec); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with banned endpoint name in transfer_data +/// +/// ### EXPECTED +/// Error BANNED_ENDPOINT_NAME +#[test] +fn test_deposit_endpoint_banned() { + let mut state = MvxEsdtSafeTestState::new(); + + let config = EsdtSafeConfig { + banned_endpoints: ManagedVec::from(vec![ManagedBuffer::from(TESTING_SC_ENDPOINT)]), + ..EsdtSafeConfig::default_config() + }; + + state + .common_setup + .deploy_mvx_esdt_safe(OptionalValue::Some(config)); + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN)); + }); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + state.complete_setup_phase(); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + let tokens_vec = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec, + Some(BANNED_ENDPOINT_NAME), + ); + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), tokens_vec); +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data only and no payments +/// +/// ### EXPECTED +/// The endpoint is called in the testing smart contract +#[test] +fn test_deposit_transfer_data_only_no_fee() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.common_setup.deploy_testing_sc(); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + PaymentsVec::new(), + None, + ); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with transfer data only, no payments and fee set +/// +/// ### EXPECTED +/// Error ERR_EMPTY_PAYMENTS +#[test] +fn test_deposit_transfer_data_only_with_fee_nothing_to_transfer() { + let mut state = MvxEsdtSafeTestState::new(); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state.deploy_contract_with_roles(Some(fee)); + state.complete_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.common_setup.deploy_testing_sc(); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + PaymentsVec::new(), + Some(ERR_EMPTY_PAYMENTS), + ); +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and fee payment +/// +/// ### EXPECTED +/// The endpoint is called in the testing smart contract +#[test] +fn test_deposit_transfer_data_only_with_fee() { + let mut state = MvxEsdtSafeTestState::new(); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state.deploy_contract_with_roles(Some(fee)); + state.complete_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + + let fee_payment = EgldOrEsdtTokenPayment::::new( + EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + 0, + fee_amount.clone(), + ); + + let payments_vec = PaymentsVec::from(vec![fee_payment]); + + state.common_setup.deploy_testing_sc(); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec, + None, + ); + + state.common_setup.check_account_single_esdt( + FEE_MARKET_ADDRESS.to_address(), + FEE_TOKEN, + 0u64, + gas_limit.into(), + ); +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and valid payment +/// +/// ### EXPECTED +/// USER's balance is updated +#[test] +fn test_deposit_fee_enabled() { + let mut state = MvxEsdtSafeTestState::new(); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state.deploy_contract_with_roles(Some(fee)); + state.complete_setup_phase(); + + state.common_setup.deploy_testing_sc(); + + let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + + let fee_payment = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FEE_TOKEN), + 0, + fee_amount.clone(), + ); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![ + fee_payment, + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec.clone(), + None, + ); + + let expected_amount_token_one = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; + + let expected_amount_token_two = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; + + let expected_amount_token_fee = BigUint::from(ONE_HUNDRED_MILLION) + - BigUint::from(payments_vec.len() - 1) * PER_TRANSFER + - BigUint::from(gas_limit) * PER_GAS; + + let expected_balances = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, expected_amount_token_one)), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, expected_amount_token_two)), + MultiValue3::from((FEE_TOKEN, 0u64, expected_amount_token_fee)), + ]; + + state + .common_setup + .check_account_multiple_esdts(OWNER_ADDRESS.to_address(), expected_balances); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and payment not enough for fee +/// +/// ### EXPECTED +/// Error PAYMENT_DOES_NOT_COVER_FEE +#[test] +fn test_deposit_payment_doesnt_cover_fee() { + let mut state = MvxEsdtSafeTestState::new(); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::from(PER_TRANSFER), + per_gas: BigUint::from(PER_GAS), + }, + }; + + state.deploy_contract_with_roles(Some(fee)); + state.complete_setup_phase(); + + state.common_setup.deploy_testing_sc(); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(10u64), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); + + let gas_limit = 10_000; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec, + Some(PAYMENT_DOES_NOT_COVER_FEE), + ); + + let tokens_vec = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), tokens_vec); +} + +/// ### TEST +/// M-ESDT_DEP_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and non-whitelisted tokens +/// +/// ### EXPECTED +/// The tokens are refunded back to the user, except the fee +#[test] +fn test_deposit_refund() { + let mut state = MvxEsdtSafeTestState::new(); + + let config = EsdtSafeConfig { + token_whitelist: ManagedVec::from(vec![EgldOrEsdtTokenIdentifier::esdt(CROWD_TOKEN_ID)]), + ..EsdtSafeConfig::default_config() + }; + + state + .common_setup + .deploy_mvx_esdt_safe(OptionalValue::Some(config)); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FEE_TOKEN), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN)); + }); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + state.complete_setup_phase(); + + state.common_setup.deploy_testing_sc(); + + let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + + let fee_payment = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FEE_TOKEN), + 0, + fee_amount.clone(), + ); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![ + fee_payment, + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + let gas_limit = 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec.clone(), + None, + ); + + let expected_balances = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::from(ONE_HUNDRED_MILLION))), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::from(ONE_HUNDRED_MILLION))), + MultiValue3::from(( + FEE_TOKEN, + 0u64, + BigUint::from(ONE_HUNDRED_MILLION - gas_limit as u32), + )), + ]; + + state + .common_setup + .check_account_multiple_esdts(OWNER_ADDRESS.to_address(), expected_balances); +} + +/// ### TEST +/// M-ESDT_DEP_OK +/// +/// ### ACTION +/// Call 'deposit()' with burn mechanism set +/// +/// ### EXPECTED +/// USER's balance is updated +#[test] +fn test_deposit_success_burn_mechanism() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.set_token_burn_mechanism_before_setup_phase(TRUSTED_TOKEN, None); + state.complete_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + let esdt_token_payment_trusted_token = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(TRUSTED_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![ + esdt_token_payment_trusted_token.clone(), + esdt_token_payment_two.clone(), + ]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec, + None, + ); + + let expected_tokens = vec![ + MultiValue3::from(( + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, + BigUint::zero(), + )), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::from(ONE_HUNDRED_THOUSAND))), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), expected_tokens); + + let tokens = vec![ + ( + EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + ONE_HUNDRED_THOUSAND.into(), + ), + (EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN), 0u64), + ]; + + state.common_setup.check_deposited_tokens_amount(tokens); +} + +/// ### TEST +/// M-ESDT_REG_OK +/// +/// ### ACTION +/// Call 'register_token()' with valid token attributes +/// +/// ### EXPECTED +/// The token is registered +#[test] +fn test_register_token_fungible_token_with_prefix() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = SOV_TOKEN; + let token_type = EsdtTokenType::Fungible; + let token_display_name = "TokenOne"; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + let num_decimals = 3; + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![log!(REGISTER_TOKEN_EVENT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + + state.register_token(register_token_args, hash_of_hashes, None, expected_logs); + + // TODO: add check for storage after callback fix +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with no prefix and type fungible +/// +/// ### EXPECTED +/// Error CANNOT_REGISTER_TOKEN +#[test] +fn test_register_token_fungible_token_no_prefix() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = FIRST_TEST_TOKEN; + let token_type = EsdtTokenType::Fungible; + let token_display_name = "TokenOne"; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + let num_decimals = 3; + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = vec![ + log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT]), + log!(REGISTER_TOKEN_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_PREFIX_FOR_REGISTER)), + ]; + state.register_token(register_token_args, hash_of_hashes, None, expected_logs); + + state + .common_setup + .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN.as_str()); +} + +/// ### TEST +/// M-ESDT_REG_OK +/// +/// ### ACTION +/// Call 'register_token()' with valid token attributes and token type DynamicNFT +/// +/// ### EXPECTED +/// The token is registered +#[ignore = "Needs system sc function fix (registerAndSetAllRolesDynamic)"] +#[test] +fn test_register_token_non_fungible_token_dynamic() { + let mut state = MvxEsdtSafeTestState::new(); + + let sov_token_id = SOV_TOKEN; + let token_type = EsdtTokenType::DynamicNFT; + let token_display_name = "TokenOne"; + let token_ticker = FIRST_TEST_TOKEN.as_str(); + let num_decimals = 3; + + let register_token_args = RegisterTokenOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(sov_token_id), + token_type, + token_display_name: token_display_name.into(), + token_ticker: token_ticker.into(), + num_decimals, + data: OperationData::new(0u64, USER_ADDRESS.to_managed_address(), None), + }; + + let token_hash = register_token_args.generate_hash(); + let hash_of_hashes = ManagedBuffer::from(&sha256(&token_hash.to_vec())); + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + let payment = + EgldOrEsdtTokenPayment::new(EGLD_000000_TOKEN_IDENTIFIER.into(), 0u64, ISSUE_COST.into()); + + let signature = state.deploy_and_complete_setup_phase(&hash_of_hashes); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(payment), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![token_hash]), + ); + + let expected_logs = + vec![log!(REGISTER_TOKEN_ENDPOINT, topics: [DEPOSIT_EVENT, EXECUTED_BRIDGE_OP_EVENT])]; + state.register_token( + register_token_args, + hash_of_hashes, + Some(INVALID_PREFIX_FOR_REGISTER), + expected_logs, + ); +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call register_token twice +/// +/// ### EXPECTED +/// The first token is registered and then error NATIVE_TOKEN_ALREADY_REGISTERED +#[test] +fn test_register_native_token_already_registered() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + let token_display_name = "TokenOne"; + let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); + + // TODO: Add check for storage after callback issue is fixed + + state.register_native_token( + FIRST_TEST_TOKEN.as_str(), + token_display_name, + egld_payment.clone(), + Some(NATIVE_TOKEN_ALREADY_REGISTERED), + ); +} + +/// ### TEST +/// M-ESDT_REG_FAIL +/// +/// ### ACTION +/// Call complete_setup_phase() with no native token +/// +/// ### EXPECTED +/// NATIVE_TOKEN_NOT_REGISTERED +#[test] +fn test_complete_setup_with_no_native_token() { + let mut state = MvxEsdtSafeTestState::new(); + state.common_setup.deploy_mvx_esdt_safe(OptionalValue::None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + assert!(state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(MvxEsdtSafeProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run() + .is_err_and(|e| e.message == NATIVE_TOKEN_NOT_REGISTERED),); +} + +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with no chain-config registered +/// +/// ### EXPECTED +/// Error CALLER_NOT_FROM_CURRENT_SOVEREIGN +#[test] +fn test_execute_operation_no_chain_config_registered() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + 0, + EsdtTokenData::default(), + ); + + let operation = Operation::new( + SOVEREIGN_RECEIVER_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + + let hash_of_hashes = state.common_setup.get_operation_hash(&operation); + + state.common_setup.deploy_header_verifier(vec![]); + + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN)), + ]; + + state.execute_operation(&hash_of_hashes, &operation, expected_logs); + + state + .common_setup + .check_operation_hash_status_is_empty(&hash_of_hashes); +} + +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with no esdt-safe-address set +/// +/// ### EXPECTED +/// Error CALLER_NOT_FROM_CURRENT_SOVEREIGN +#[test] +fn test_execute_operation_no_esdt_safe_registered() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + 0, + EsdtTokenData::default(), + ); + + let operation = Operation::new( + SOVEREIGN_RECEIVER_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + + let hash_of_hashes = state.common_setup.get_operation_hash(&operation); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig]); + + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(CALLER_NOT_FROM_CURRENT_SOVEREIGN)), + ]; + + state.execute_operation(&hash_of_hashes, &operation, expected_logs); + + state + .common_setup + .check_operation_hash_status_is_empty(&hash_of_hashes); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with valid operation +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract +#[test] +fn test_execute_operation_success() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let token_data = EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_THOUSAND), + ..Default::default() + }; + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + 0, + token_data, + ); + + let gas_limit = 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + ManagedVec::from_single_item(EgldOrEsdtTokenPayment::new( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + )), + None, + ); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with payments and transfer data that exceeds the max gas limit +/// +/// ### EXPECTED +/// Error GAS_LIMIT_TOO_HIGH, operation does not execute +#[test] +fn test_execute_operation_transfer_data_gas_limit_too_high_with_payments() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let token_data = EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_THOUSAND), + ..Default::default() + }; + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + 0, + token_data, + ); + + let gas_limit = MAX_GAS_PER_TRANSACTION + 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!( + EXECUTE_BRIDGE_OPS_ENDPOINT, + topics: [EXECUTED_BRIDGE_OP_EVENT], + data: Some(GAS_LIMIT_TOO_HIGH) + )]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with payment containing the registered token +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract +#[test] +fn test_execute_operation_with_native_token_success() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let token_data = EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_THOUSAND), + ..Default::default() + }; + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(NATIVE_TEST_TOKEN), + 0, + token_data, + ); + + let gas_limit = 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); + + state.common_setup.check_account_single_esdt( + TESTING_SC_ADDRESS.to_address(), + NATIVE_TEST_TOKEN, + 0u64, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' after setting the burn mechanism without prior deposit +/// +/// ### EXPECTED +/// The operation executes successfully with minted tokens +#[test] +fn test_execute_operation_burn_mechanism_without_deposit() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + 0, + EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_THOUSAND), + ..Default::default() + }, + ); + + let burn_operation = SetBurnMechanismOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + nonce: state.common_setup.next_operation_nonce(), + }; + let burn_operation_hash = burn_operation.generate_hash(); + let burn_hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&burn_operation_hash.to_vec())); + + let operation = Operation::new( + SOVEREIGN_RECEIVER_ADDRESS.to_managed_address(), + vec![payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + // Deploy and register validators + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + let (signature_burn, public_keys_burn) = state + .common_setup + .get_sig_and_pub_keys(1, &burn_hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.register( + public_keys_burn.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + state.common_setup.full_bitmap(1), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])), + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_burn, + &burn_hash_of_hashes, + state.common_setup.bitmap_for_signers(&[1]), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![burn_operation_hash])), + ); + + state.set_token_burn_mechanism(&burn_hash_of_hashes, burn_operation); + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(DEPOSIT_AMOUNT_NOT_ENOUGH)), + ]; + state.execute_operation(&hash_of_hashes, &operation, expected_logs); + + state + .common_setup + .check_operation_hash_status_is_empty(&operation_hash); + state.common_setup.check_account_single_esdt( + TESTING_SC_ADDRESS.to_address(), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, + BigUint::zero(), + ); +} + +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with no tokens and no transfer data +/// +/// ### EXPECTED +/// Error NOTHING_TO_TRANSFER +#[test] +fn test_execute_no_transfer_data_no_token_transfer() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + ManagedVec::new(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!( + EXECUTE_BRIDGE_OPS_ENDPOINT, + topics: [EXECUTED_BRIDGE_OP_EVENT], + data: Some(NOTHING_TO_TRANSFER) + )]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with transfer data only +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract +#[test] +fn test_execute_operation_only_transfer_data_no_fee() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let gas_limit = 40_000_000u64; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + ManagedVec::new(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_FAIL +/// +/// ### ACTION +/// Call 'execute_operation()' with only transfer data whose gas limit exceeds the max threshold +/// +/// ### EXPECTED +/// Error GAS_LIMIT_TOO_HIGH, no SC call is performed +#[test] +fn test_execute_operation_only_transfer_data_gas_limit_too_high() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let gas_limit = MAX_GAS_PER_TRANSACTION + 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + ManagedVec::new(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!( + EXECUTE_BRIDGE_OPS_ENDPOINT, + topics: [EXECUTED_BRIDGE_OP_EVENT], + data: Some(GAS_LIMIT_TOO_HIGH) + )]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with burn mechanism after deposit +/// +/// ### EXPECTED +/// The operation executes successfully, tokens are burned +#[test] +fn test_execute_operation_success_burn_mechanism() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let amount = BigUint::from(ONE_HUNDRED_THOUSAND); + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + 0, + EsdtTokenData { + amount: amount.clone(), + ..Default::default() + }, + ); + + let burn_operation = SetBurnMechanismOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + nonce: state.common_setup.next_operation_nonce(), + }; + let burn_operation_hash = burn_operation.generate_hash(); + let burn_hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&burn_operation_hash.to_vec())); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![payment.clone()].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + let (signature_burn, public_keys_burn) = state + .common_setup + .get_sig_and_pub_keys(1, &burn_hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.register( + public_keys_burn.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::from(vec![payment]), + None, + ); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + state.common_setup.full_bitmap(1), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])), + ); + + state + .common_setup + .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_burn, + &burn_hash_of_hashes, + state.common_setup.bitmap_for_signers(&[1]), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![burn_operation_hash])), + ); + + state.set_token_burn_mechanism(&burn_hash_of_hashes, burn_operation); + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + state.execute_operation(&hash_of_hashes, &operation, expected_logs); + + state + .common_setup + .world + .check_account(OWNER_ADDRESS) + .esdt_balance( + EsdtTokenIdentifier::from(TRUSTED_TOKEN), + &(BigUint::from(ONE_HUNDRED_MILLION) - &amount), + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, + BigUint::zero(), + ); + + state + .common_setup + .check_deposited_tokens_amount(vec![(EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), 0)]); + + state.common_setup.check_account_single_esdt( + TESTING_SC_ADDRESS.to_address(), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, + amount, + ); + + state + .common_setup + .check_operation_hash_status_is_empty(&operation_hash); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' after switching between LOCK and BURN mechanisms +/// +/// ### EXPECTED +/// - Operations execute successfully in different mechanism states +/// - Token balances are tracked correctly during mechanism switches +#[test] +fn test_deposit_execute_switch_mechanism() { + let mut state = MvxEsdtSafeTestState::new(); + + // === Initial Setup === + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let chain_config_config = SovereignConfig { + max_validators: 4, + ..SovereignConfig::default_config_for_test() + }; + state + .common_setup + .deploy_chain_config(OptionalValue::Some(chain_config_config), None); + + let trusted_token_id = TRUSTED_TOKEN; + let execute_amount = 500u64; + let deposit_amount = 1000u64; + + // === Setup Operations === + let execute_payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + 0, + EsdtTokenData { + amount: BigUint::from(execute_amount), + ..Default::default() + }, + ); + + let burn_operation = SetBurnMechanismOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + nonce: state.common_setup.next_operation_nonce(), + }; + let burn_operation_hash = burn_operation.generate_hash(); + let burn_operation_hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&burn_operation_hash.to_vec())); + let (signature_burn, pub_keys_burn) = state + .common_setup + .get_sig_and_pub_keys(1, &burn_operation_hash_of_hashes); + state.common_setup.register( + pub_keys_burn.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + let operation_one = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![execute_payment.clone()].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + let operation_one_hash = state.common_setup.get_operation_hash(&operation_one); + let hash_of_hashes_one = ManagedBuffer::new_from_bytes(&sha256(&operation_one_hash.to_vec())); + let (signature_one, pub_keys_one) = state + .common_setup + .get_sig_and_pub_keys(1, &hash_of_hashes_one); + state.common_setup.register( + pub_keys_one.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + let lock_operation = SetLockMechanismOperation { + token_id: EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + nonce: state.common_setup.next_operation_nonce(), + }; + let lock_operation_hash = lock_operation.generate_hash(); + let lock_operation_hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&lock_operation_hash.to_vec())); + let (signature_lock, pub_keys_lock) = state + .common_setup + .get_sig_and_pub_keys(1, &lock_operation_hash_of_hashes); + state.common_setup.register( + pub_keys_lock.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + let operation_two = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![execute_payment].into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ), + ); + let operation_two_hash = state.common_setup.get_operation_hash(&operation_two); + let hash_of_hashes_two = ManagedBuffer::new_from_bytes(&sha256(&operation_two_hash.to_vec())); + let (signature_two, pub_keys_two) = state + .common_setup + .get_sig_and_pub_keys(1, &hash_of_hashes_two); + state.common_setup.register( + pub_keys_two.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + // === Complete Remaining Setup === + state.common_setup.complete_chain_config_setup_phase(); + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + state.common_setup.deploy_testing_sc(); + + let deposit_payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + 0, + EsdtTokenData { + amount: BigUint::from(deposit_amount), + ..Default::default() + }, + ); + + // === Test Flow === + // 1. First deposit (default LOCK mechanism) + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::from(vec![deposit_payment.clone()]), + None, + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(deposit_amount), + ); + + // 2. Switch to BURN mechanism (uses validator 0) + let burn_bitmap = state.common_setup.bitmap_for_signers(&[0]); + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_burn, + &burn_operation_hash_of_hashes, + burn_bitmap, + 0, + MultiValueEncoded::from(ManagedVec::from(vec![burn_operation_hash.clone()])), + ); + state.set_token_burn_mechanism(&burn_operation_hash_of_hashes, burn_operation); + + let mut expected_deposited = deposit_amount; + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + expected_deposited, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::zero(), + ); + + // 3. Execute operation 1 (BURN mechanism, uses validator 1) + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_one, + &hash_of_hashes_one, + state.common_setup.bitmap_for_signers(&[1]), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![operation_one_hash])), + ); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + state.execute_operation(&hash_of_hashes_one, &operation_one, expected_logs); + + let mut expected_receiver = execute_amount; + expected_deposited -= execute_amount; + + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + expected_deposited, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::zero(), + ); + + // 4. Second deposit (BURN mechanism) + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::from(vec![deposit_payment.clone()]), + None, + ); + + expected_deposited += deposit_amount; + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + expected_deposited, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::zero(), + ); + + // 5. Switch back to LOCK mechanism (uses validator 2) + let lock_bitmap = state.common_setup.bitmap_for_signers(&[2]); + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_lock, + &lock_operation_hash_of_hashes, + lock_bitmap, + 0, + MultiValueEncoded::from(ManagedVec::from(vec![lock_operation_hash.clone()])), + ); + state.set_token_lock_mechanism(&lock_operation_hash_of_hashes, lock_operation); + + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + 0, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(expected_deposited), + ); + + // 6. Execute operation 2 (LOCK mechanism, uses validator 3) + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_two, + &hash_of_hashes_two, + state.common_setup.bitmap_for_signers(&[3]), + 0, + MultiValueEncoded::from(ManagedVec::from(vec![operation_two_hash])), + ); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + state.execute_operation(&hash_of_hashes_two, &operation_two, expected_logs); + + expected_receiver += execute_amount; + expected_deposited -= execute_amount; + + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + 0, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(expected_deposited), + ); + state.common_setup.check_account_single_esdt( + TESTING_SC_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(expected_receiver), + ); + + // 7. Third deposit (LOCK mechanism) + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::from(vec![deposit_payment]), + None, + ); + + expected_deposited += deposit_amount; + + state.common_setup.check_deposited_tokens_amount(vec![( + EgldOrEsdtTokenIdentifier::esdt(trusted_token_id), + 0, + )]); + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(expected_deposited), + ); + state.common_setup.check_account_single_esdt( + TESTING_SC_ADDRESS.to_address(), + TestTokenIdentifier::new(trusted_token_id), + 0, + BigUint::from(expected_receiver), + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with empty payments +/// +/// ### EXPECTED +/// The operation is executed in the testing smart contract +#[test] +fn test_execute_operation_no_payments() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + let gas_limit = 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + + let transfer_data = TransferData::new(gas_limit, function, args); + + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + ManagedVec::new(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with empty payments and wrong endpoint +/// +/// ### EXPECTED +/// The operation is not executed in the testing smart contract +#[test] +fn test_execute_operation_no_payments_failed_event() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let gas_limit = 1; + let function = ManagedBuffer::::from(WRONG_ENDPOINT_NAME); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + let transfer_data = TransferData::new(gas_limit, function, args); + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + ManagedVec::new(), + OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ), + ); + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![ + log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_FUNCTION_NOT_FOUND)), + ]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); +} + +/// ### TEST +/// M-NATIVE_ESDT_EXEC_OK +/// +/// ### ACTION +/// Call 'execute_operation()' with native esdt payment and wrong endpoint +/// +/// ### EXPECTED +/// The operation is not executed in the testing smart contract +/// Native ESDT should be burned +#[test] +fn test_execute_operation_native_token_failed_event() { + let mut state = MvxEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let token_data = EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_TOKENS), + ..Default::default() + }; + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::esdt(NATIVE_TEST_TOKEN), + 0, + token_data, + ); + + let gas_limit = 1; + let function = ManagedBuffer::::from(WRONG_ENDPOINT_NAME); + let args = + ManagedVec::>::from(vec![ManagedBuffer::from("1")]); + let transfer_data = TransferData::new(gas_limit, function, args); + let operation_data = OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + Some(transfer_data), + ); + let operation = Operation::new( + TESTING_SC_ADDRESS.to_managed_address(), + vec![payment].into(), + operation_data, + ); + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.deploy_testing_sc(); + + let expected_logs = vec![ + log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(INVALID_FUNCTION_NOT_FOUND)), + ]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + true, + ); + + let addresses = [OWNER_ADDRESS.to_address(), TESTING_SC_ADDRESS.to_address()]; + for address in &addresses { + state.common_setup.check_account_single_esdt( + address.clone(), + NATIVE_TEST_TOKEN, + 0u64, + BigUint::zero(), + ); + } +} + +/// ### TEST +/// M-ESDT_SET_BURN_FAIL +/// +/// ### ACTION +/// Call 'set_token_burn_mechanism()' without the proper roles +/// +/// ### EXPECTED +/// Error MINT_AND_BURN_ROLES_NOT_FOUND +#[test] +fn test_set_token_burn_mechanism_no_roles() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.set_token_burn_mechanism_before_setup_phase("WEGLD", Some(MINT_AND_BURN_ROLES_NOT_FOUND)); +} + +/// ### TEST +/// M-ESDT_SET_BURN_FAIL +/// +/// ### ACTION +/// Call 'set_token_burn_mechanism()' without a trusted token id +/// +/// ### EXPECTED +/// Error TOKEN_ID_IS_NOT_TRUSTED +#[test] +fn test_set_token_burn_mechanism_token_not_trusted() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.set_token_burn_mechanism_before_setup_phase( + FIRST_TEST_TOKEN.as_str(), + Some(TOKEN_ID_IS_NOT_TRUSTED), + ); +} + +/// ### TEST +/// M-ESDT_SET_BURN_OK +/// +/// ### ACTION +/// Call 'set_token_burn_mechanism()' with a trusted token id +/// +/// ### EXPECTED +/// The trusted token has the burn mechanism set +#[test] +fn test_set_token_burn_mechanism() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state.set_token_burn_mechanism_before_setup_phase(TRUSTED_TOKEN, None); + + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc + .burn_mechanism_tokens() + .contains(&EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN))) + }); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 0u64, + BigUint::zero(), + ); +} + +/// ### TEST +/// M-ESDT_SET_BURN_OK +/// +/// ### ACTION +/// Call both 'set_token_burn_mechanism()' and 'set_token_lock_mechanism()' with a trusted token id. +/// +/// ### EXPECTED +/// The trusted token has the lock mechanism set +#[test] +fn test_set_token_lock_mechanism() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + state.set_token_burn_mechanism_before_setup_phase(TRUSTED_TOKEN, None); + state.set_token_lock_mechanism_before_setup_phase(TRUSTED_TOKEN, None); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc.burn_mechanism_tokens().is_empty()) + }); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + TestTokenIdentifier::new(TRUSTED_TOKEN), + 100u64, + BigUint::zero(), + ); +} + +/// ### TEST +/// M-ESDT_UPDATE_CONFIG_FAIL +/// +/// ### ACTION +/// Call `update_esdt_safe_config()` before setup phase completion +/// +/// ### EXPECTED +/// ERROR SETUP_PHASE_NOT_COMPLETED +#[test] +fn test_update_config_setup_phase_not_completed() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + let esdt_safe_config = EsdtSafeConfig::default_config(); + + let nonce = state.common_setup.next_operation_nonce(); + state.update_esdt_safe_config( + &ManagedBuffer::new(), + UpdateEsdtSafeConfigOperation { + esdt_safe_config, + nonce, + }, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// M-ESDT_UPDATE_CONFIG_FAIL +/// +/// ### ACTION +/// Call `update_esdt_safe_config()` before registering operation +/// +/// ### EXPECTED +/// ERROR CURRENT_OPERATION_NOT_REGISTERED +#[test] +fn test_update_config_operation_not_registered() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + let esdt_safe_config = EsdtSafeConfig::default_config(); + + let nonce = state.common_setup.next_operation_nonce(); + state.update_esdt_safe_config( + &ManagedBuffer::new(), + UpdateEsdtSafeConfigOperation { + esdt_safe_config, + nonce, + }, + Some(CURRENT_OPERATION_NOT_REGISTERED), + ); +} + +/// ### TEST +/// M-ESDT_UPDATE_CONFIG_ERROR +/// +/// ### ACTION +/// Call `update_esdt_safe_config()` with an invalid config +/// +/// ### EXPECTED +/// failedBridgeOp event is emitted +#[test] +fn test_update_config_invalid_config() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let esdt_safe_config = EsdtSafeConfig { + max_tx_gas_limit: MAX_GAS_PER_TRANSACTION + 1, + ..EsdtSafeConfig::default_config() + }; + let update_config_operation = UpdateEsdtSafeConfigOperation { + esdt_safe_config: esdt_safe_config.clone(), + nonce: state.common_setup.next_operation_nonce(), + }; + + let config_hash = update_config_operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&config_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![config_hash]), + ); + + state.update_esdt_safe_config( + &hash_of_hashes, + update_config_operation, + Some(MAX_GAS_LIMIT_PER_TX_EXCEEDED), + ); +} + +/// ### TEST +/// M-ESDT_UPDATE_CONFIG_OK +/// +/// ### ACTION +/// Call `update_esdt_safe_config()` +/// +/// ### EXPECTED +/// EsdtSafeConfig is updated and executedBridgeOp is emitted +#[test] +fn test_update_config() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let esdt_safe_config = EsdtSafeConfig { + max_tx_gas_limit: ONE_HUNDRED_THOUSAND as u64, + address_blacklist: ManagedVec::from_iter(vec![OWNER_ADDRESS.to_managed_address()]), + ..EsdtSafeConfig::default_config() + }; + let update_config_operation = UpdateEsdtSafeConfigOperation { + esdt_safe_config, + nonce: state.common_setup.next_operation_nonce(), + }; + + let config_hash = update_config_operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&config_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![config_hash]), + ); + + state.update_esdt_safe_config(&hash_of_hashes, update_config_operation.clone(), None); + + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + let config = sc.esdt_safe_config().get(); + assert!( + config.max_tx_gas_limit == ONE_HUNDRED_THOUSAND as u64 + && config + .address_blacklist + .contains(&OWNER_ADDRESS.to_managed_address()) + ); + }); + + let payment = EgldOrEsdtTokenPayment::egld_payment(BigUint::zero()); + let payment_vec = PaymentsVec::from(vec![payment]); + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payment_vec, + Some(CALLER_IS_BLACKLISTED), + ); + + state + .common_setup + .world + .query() + .to(HEADER_VERIFIER_ADDRESS) + .whitebox(header_verifier::contract_obj, |sc| { + let config_hash_whitebox = + ManagedBuffer::from(update_config_operation.generate_hash().to_vec()); + let hash_of_hashes_whitebox = + ManagedBuffer::new_from_bytes(&sha256(&config_hash_whitebox.to_vec())); + assert!(sc + .operation_hash_status(&hash_of_hashes_whitebox, &config_hash_whitebox) + .is_empty()) + }); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Call `execute_operation()` when the contract is paused +/// +/// ### EXPECTED +/// The operation is executed and the tokens are burned from SC +#[test] +fn test_execute_paused_refund() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.complete_setup_phase(); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.paused_status().set(true); + }); + + let token_data = EsdtTokenData { + amount: BigUint::from(100u64), + ..Default::default() + }; + + let payment = OperationEsdtPayment::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_bytes()), + 0, + token_data, + ); + let operation_data = OperationData::new( + state.common_setup.next_operation_nonce(), + OWNER_ADDRESS.to_managed_address(), + None, + ); + let operation = Operation { + to: USER_ADDRESS.to_managed_address(), + tokens: vec![payment].into(), + data: operation_data, + }; + + let operation_hash = operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + let (signature, bls_key) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + let egld_payment = EgldOrEsdtTokenPayment::egld_payment(BigUint::default()); + let payments_vec = PaymentsVec::from(vec![egld_payment]); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + state + .common_setup + .register(&bls_key[0], &payments_vec, None); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(ESDT_SAFE_STILL_PAUSED)), + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [DEPOSIT_EVENT, FIRST_TEST_TOKEN.as_str()]), + ]; + + state.register_and_execute_operation( + &operation, + &hash_of_hashes, + signature, + 1, + expected_logs, + false, + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0, + BigUint::zero(), + ); +} + +/// ### TEST +/// M-ESDT_PAUSE_STATUS_OK +/// +/// ### ACTION +/// Call `switch_pause_status()` when the contract is unpaused +/// +/// ### EXPECTED +/// Contract is paused and deposit endpoint fails +#[test] +fn test_pause_contract() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let nonce = state.common_setup.next_operation_nonce(); + let pause_operation = PauseStatusOperation { + status: true, + nonce, + }; + let operation_hash = pause_operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.complete_setup_phase(); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![operation_hash]), + ); + + state.switch_pause_status( + &hash_of_hashes, + pause_operation, + vec![log!( + PAUSE_CONTRACT_LOG, + topics: [EXECUTED_BRIDGE_OP_EVENT] + )], + ); + + state + .common_setup + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + assert!(sc.is_paused()); + }); + + let egld_payment = EgldOrEsdtTokenPayment::egld_payment(BigUint::from(100u64)); + let payments_vec = PaymentsVec::from(vec![egld_payment]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec, + Some(ESDT_SAFE_STILL_PAUSED), + ); +} + +/// ### TEST +/// M-ESDT_EXEC_OK +/// +/// ### ACTION +/// Execute a bridge operation with mixed token payments, including one without prefunded liquidity. +/// +/// ### EXPECTED +/// Contract emits the partial-execution logs and refunds earlier mints, leaving balances unchanged. +#[test] +fn test_execute_operation_partial_execution() { + let mut state = MvxEsdtSafeTestState::new(); + state.deploy_contract_with_roles(None); + state.set_token_burn_mechanism_before_setup_phase(TRUSTED_TOKEN, None); + state.complete_setup_phase(); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + let token_pairs = [ + (FIRST_TOKEN_ID, SOV_FIRST_TOKEN_ID), + (SECOND_TOKEN_ID, SOV_SECOND_TOKEN_ID), + ]; + + for (mvx_token, sov_token) in token_pairs { + sc.multiversx_to_sovereign_token_id_mapper(&EgldOrEsdtTokenIdentifier::esdt( + mvx_token, + )) + .set(EgldOrEsdtTokenIdentifier::esdt( + sov_token.to_token_identifier(), + )); + + sc.sovereign_to_multiversx_token_id_mapper(&EgldOrEsdtTokenIdentifier::esdt( + sov_token.to_token_identifier(), + )) + .set(EgldOrEsdtTokenIdentifier::esdt( + mvx_token.to_token_identifier(), + )); + } + }); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let token_data = EsdtTokenData { + amount: BigUint::from(ONE_HUNDRED_THOUSAND), + ..Default::default() + }; + + let token_ids = [ + EgldOrEsdtTokenIdentifier::esdt(SOV_FIRST_TOKEN_ID), + EgldOrEsdtTokenIdentifier::esdt(TRUSTED_TOKEN), + EgldOrEsdtTokenIdentifier::esdt(SOV_SECOND_TOKEN_ID), + ]; + + let payments: Vec<_> = token_ids + .iter() + .map(|token_id| OperationEsdtPayment::new(token_id.clone(), 0, token_data.clone())) + .collect(); + + let operation = Operation::new( + USER_ADDRESS.to_managed_address(), + payments.into(), + OperationData::new( + state.common_setup.next_operation_nonce(), + USER_ADDRESS.to_managed_address(), + None, + ), + ); + let operation_hash = state.common_setup.get_operation_hash(&operation); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::ESDTSafe]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let expected_logs = vec![ + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(DEPOSIT_AMOUNT_NOT_ENOUGH)), + log!(EXECUTE_BRIDGE_OPS_ENDPOINT, topics: [DEPOSIT_EVENT, SOV_FIRST_TOKEN_ID.as_str(), TRUSTED_TOKEN, SOV_SECOND_TOKEN_ID.as_str()]), + ]; + + let bitmap = state.common_setup.full_bitmap(1); + let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); + + state.common_setup.register_operation( + USER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + 0, + operations_hashes, + ); + + state.execute_operation(&hash_of_hashes, &operation, expected_logs); + + let addresses = [USER_ADDRESS.to_address(), ESDT_SAFE_ADDRESS.to_address()]; + let tokens = [ + FIRST_TOKEN_ID, + SECOND_TOKEN_ID, + TestTokenIdentifier::new(TRUSTED_TOKEN), + ]; + + for address in &addresses { + for token_id in &tokens { + state.common_setup.check_account_single_esdt( + address.clone(), + *token_id, + 0, + BigUint::zero(), + ); + } + } +} diff --git a/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_unit_tests.rs b/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_unit_tests.rs deleted file mode 100644 index 075c63aeb..000000000 --- a/mvx-esdt-safe/tests/mvx_esdt_safe_blackbox_unit_tests.rs +++ /dev/null @@ -1,1923 +0,0 @@ -use common_test_setup::constants::{ - ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, HEADER_VERIFIER_ADDRESS, - ONE_HUNDRED_MILLION, ONE_HUNDRED_THOUSAND, OWNER_ADDRESS, SECOND_TEST_TOKEN, SOV_TOKEN, - TESTING_SC_ADDRESS, USER, -}; -use common_test_setup::RegisterTokenArgs; -use cross_chain::storage::CrossChainStorage; -use cross_chain::{DEFAULT_ISSUE_COST, MAX_GAS_PER_TRANSACTION}; -use error_messages::{ - BANNED_ENDPOINT_NAME, CANNOT_REGISTER_TOKEN, ERR_EMPTY_PAYMENTS, GAS_LIMIT_TOO_HIGH, - INVALID_TYPE, MAX_GAS_LIMIT_PER_TX_EXCEEDED, MINT_AND_BURN_ROLES_NOT_FOUND, - NOTHING_TO_TRANSFER, NO_ESDT_SAFE_ADDRESS, PAYMENT_DOES_NOT_COVER_FEE, TOKEN_ID_IS_NOT_TRUSTED, - TOKEN_IS_FROM_SOVEREIGN, TOO_MANY_TOKENS, -}; -use header_verifier::OperationHashStatus; -use multiversx_sc::types::MultiValueEncoded; -use multiversx_sc::{ - imports::{MultiValue3, OptionalValue}, - types::{ - BigUint, EsdtTokenData, EsdtTokenPayment, EsdtTokenType, ManagedBuffer, ManagedVec, - TestTokenIdentifier, TokenIdentifier, - }, -}; -use multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; -use multiversx_sc_scenario::{api::StaticApi, ScenarioTxWhitebox}; -use mvx_esdt_safe::bridging_mechanism::{BridgingMechanism, TRUSTED_TOKEN_IDS}; -use mvx_esdt_safe_blackbox_setup::MvxEsdtSafeTestState; -use proxies::fee_market_proxy::{FeeStruct, FeeType}; -use structs::configs::SovereignConfig; -use structs::operation::TransferData; -use structs::{ - aliases::PaymentsVec, - configs::EsdtSafeConfig, - operation::{Operation, OperationData, OperationEsdtPayment}, -}; -mod mvx_esdt_safe_blackbox_setup; - -#[test] -fn deploy() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); -} - -/// Test that deploy fails when the gas limit in the config is too high -#[test] -fn deploy_invalid_config() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - MAX_GAS_PER_TRANSACTION + 1, - ManagedVec::new(), - ); - - state.update_configuration(config, Some(MAX_GAS_LIMIT_PER_TX_EXCEEDED)); -} - -/// This Test checks the flow for registering an invalid token -#[test] -fn register_token_invalid_type() { - let mut state = MvxEsdtSafeTestState::new(); - let config = OptionalValue::Some(EsdtSafeConfig::default_config()); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, config); - - let sov_token_id = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let token_type = EsdtTokenType::Invalid; - let token_display_name = "TokenOne"; - let num_decimals = 3; - let token_ticker = FIRST_TEST_TOKEN; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token( - register_token_args, - egld_payment, - Some(CANNOT_REGISTER_TOKEN), - ); -} - -/// This Test checks the flow for registering an invalid token with prefix -#[test] -fn register_token_invalid_type_with_prefix() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(SOV_TOKEN); - let token_type = EsdtTokenType::Invalid; - let token_display_name = "TokenOne"; - let num_decimals = 3; - let token_ticker = FIRST_TEST_TOKEN; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token(register_token_args, egld_payment, Some(INVALID_TYPE)); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -/// This Test checks the flow for registering a token that is not native -#[test] -fn register_token_not_native() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(SECOND_TEST_TOKEN); - let token_type = EsdtTokenType::Fungible; - let token_display_name = "TokenOne"; - let num_decimals = 3; - let token_ticker = FIRST_TEST_TOKEN; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token( - register_token_args, - egld_payment, - Some(CANNOT_REGISTER_TOKEN), - ); -} - -/// This Test checks the flow for registering a fungible token -#[test] -fn register_token_fungible_token() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(SOV_TOKEN); - let token_type = EsdtTokenType::Fungible; - let token_display_name = "TokenOne"; - let token_ticker = FIRST_TEST_TOKEN; - let num_decimals = 3; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token(register_token_args, egld_payment, None); -} - -/// Test that register token works with a non-fungible token type -#[test] -fn register_token_nonfungible_token() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let token_type = EsdtTokenType::NonFungible; - let token_display_name = "TokenOne"; - let num_decimals = 0; - let token_ticker = FIRST_TEST_TOKEN; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token( - register_token_args, - egld_payment, - Some(CANNOT_REGISTER_TOKEN), - ); -} - -/// Test that deposit fails when there is no payment for transfer -#[test] -fn deposit_nothing_to_transfer() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::new(), - Some(NOTHING_TO_TRANSFER), - None, - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -/// Test that deposit fails when there are too many tokens in the payment (limit being the MAX_TRANSFERS_PER_TX) -#[test] -fn deposit_too_many_tokens() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - let esdt_token_payment = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::default(), - ); - - let payments_vec = PaymentsVec::from(vec![esdt_token_payment; 11]); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - payments_vec, - Some(TOO_MANY_TOKENS), - None, - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -/// Test that deposit with no transfer data succeeds -#[test] -fn deposit_no_transfer_data() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - payments_vec, - None, - Some("deposit"), - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -/// Test that deposit fails when the gas limit is too high -#[test] -fn deposit_gas_limit_too_high() { - let mut state = MvxEsdtSafeTestState::new(); - - let config = EsdtSafeConfig::new(ManagedVec::new(), ManagedVec::new(), 1, ManagedVec::new()); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - state.common_setup.deploy_testing_sc(); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec, - Some(GAS_LIMIT_TOO_HIGH), - None, - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -/// Test that deposit fails when the endpoint is banned -#[test] -fn deposit_endpoint_banned() { - let mut state = MvxEsdtSafeTestState::new(); - - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::from(vec![ManagedBuffer::from("hello")]), - ); - - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec, - Some(BANNED_ENDPOINT_NAME), - None, - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); -} - -// Test that deposit with no transfer data, no fee and no payment fails -#[test] -fn deposit_no_transfer_data_no_fee() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::new(), - Some(NOTHING_TO_TRANSFER), - None, - ); -} - -/// This test checks the flow for a deposit with transfer data only -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 3. Deploy the Testing smart contract -/// 6. Create the ESDT token payments -/// 7. Create the payments vector -/// 8. Create the transfer data -/// 9. Call the deposit function -/// 10. Check the balances of the accounts -#[test] -fn deposit_transfer_data_only_no_fee() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - PaymentsVec::new(), - None, - Some("scCall"), - ); -} - -/// This test check the flow for a deposit with transfer data that fails -#[test] -fn deposit_transfer_data_only_with_fee_nothing_to_transfer() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FEE_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FEE_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - PaymentsVec::new(), - Some(ERR_EMPTY_PAYMENTS), - None, - ); -} - -/// This test check the flow for a deposit with transfer data only and the fee is enabled -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Deploy the Fee-Market smart contract -/// 3. Deploy the Testing smart contract -/// 4. Set the Fee-Market address -/// 5. Create the fee payment -/// 6. Create the ESDT token payments -/// 7. Create the payments vector -/// 8. Create the transfer data -/// 9. Call the deposit function -/// 10. Check the balances of the accounts -#[test] -fn deposit_transfer_data_only_with_fee() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FEE_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FEE_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); - - EsdtTokenPayment::::new(TokenIdentifier::from(FEE_TOKEN), 0, fee_amount.clone()); - let fee_payment = - EsdtTokenPayment::::new(TokenIdentifier::from(FEE_TOKEN), 0, fee_amount.clone()); - - let payments_vec = PaymentsVec::from(fee_payment); - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec, - None, - Some("scCall"), - ); -} - -/// This test check the flow for a deposit when the fee is enabled -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Deploy the Fee-Market smart contract -/// 3. Deploy the Testing smart contract -/// 4. Set the Fee-Market address -/// 5. Create the fee payment -/// 6. Create the ESDT token payments -/// 7. Create the payments vector -/// 8. Create the transfer data -/// 9. Call the deposit function -/// 10. Check the balances of the accounts -#[test] -fn deposit_fee_enabled() { - let mut state = MvxEsdtSafeTestState::new(); - - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FEE_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FEE_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); - - let fee_payment = - EsdtTokenPayment::::new(TokenIdentifier::from(FEE_TOKEN), 0, fee_amount.clone()); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![ - fee_payment, - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec.clone(), - None, - Some("deposit"), - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(FIRST_TEST_TOKEN), - expected_amount_token_one, - ); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(SECOND_TEST_TOKEN), - expected_amount_token_two, - ); - - let expected_amount_token_fee = BigUint::from(ONE_HUNDRED_MILLION) - - BigUint::from(payments_vec.len() - 1) * per_transfer - - BigUint::from(gas_limit) * per_gas; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(TokenIdentifier::from(FEE_TOKEN), expected_amount_token_fee); -} - -/// Test that deposit fails when the payment does not cover the fee -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Deploy the Fee-Market smart contract -/// 3. Deploy the Testing smart contract -/// 4. Set the Fee-Market address -/// 5. Create the fee payment -/// 6. Create the ESDT token payments -/// 7. Create the payments vector -/// 8. Create the transfer data -/// 9. Call the deposit function -/// 10. Check the balances of the accounts -#[test] -fn deposit_payment_doesnt_cover_fee() { - let mut state = MvxEsdtSafeTestState::new(); - - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FIRST_TEST_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FIRST_TEST_TOKEN), - per_transfer: BigUint::from(1u64), - per_gas: BigUint::from(1u64), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![esdt_token_payment_one, esdt_token_payment_two]); - - let gas_limit = 10_000; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec, - Some(PAYMENT_DOES_NOT_COVER_FEE), - None, - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(FIRST_TEST_TOKEN); - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(SECOND_TEST_TOKEN); -} - -/// Test that after deposit fails the tokens are refunded -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Deploy the Fee-Market smart contract -/// 3. Deploy the Testing smart contract -/// 4. Set the Fee-Market address -/// 5. Create the fee payment -/// 6. Create the ESDT token payments -/// 7. Create the payments vector -/// 8. Create the transfer data -/// 9. Call the deposit function -/// 10. Check the logs -/// 11. Check the balances of the accounts -#[test] -fn deposit_refund() { - let mut state = MvxEsdtSafeTestState::new(); - - let config = EsdtSafeConfig::new( - ManagedVec::new(), - ManagedVec::new(), - 50_000_000, - ManagedVec::new(), - ); - - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - - let fee = FeeStruct { - base_token: TokenIdentifier::from(FEE_TOKEN), - fee_type: FeeType::Fixed { - token: TokenIdentifier::from(FEE_TOKEN), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); - - let fee_payment = - EsdtTokenPayment::::new(TokenIdentifier::from(FEE_TOKEN), 0, fee_amount.clone()); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let payments_vec = PaymentsVec::from(vec![ - fee_payment, - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - let gas_limit = 1; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec.clone(), - None, - Some("deposit"), - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(FIRST_TEST_TOKEN), - &expected_amount_token_one, - ); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(SECOND_TEST_TOKEN), - &expected_amount_token_two, - ); - - let expected_amount_token_fee = BigUint::from(ONE_HUNDRED_MILLION) - - BigUint::from(payments_vec.len() - 1) * per_transfer - - BigUint::from(gas_limit) * per_gas; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(TokenIdentifier::from(FEE_TOKEN), expected_amount_token_fee); -} - -/// Test that deposit with a burn mechanism works -#[test] -fn deposit_success_burn_mechanism() { - let mut state = MvxEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - - let esdt_token_payment_trusted_token = EsdtTokenPayment::::new( - TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - TokenIdentifier::from(SECOND_TEST_TOKEN), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![ - esdt_token_payment_trusted_token.clone(), - esdt_token_payment_two.clone(), - ]); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - payments_vec, - None, - Some("deposit"), - ); - - state - .common_setup - .check_multiversx_to_sovereign_token_id_mapper_is_empty(TRUSTED_TOKEN_IDS[0]); - - state.common_setup.check_sc_esdt_balance( - vec![ - MultiValue3::from((TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), 0u64, 0u64)), - MultiValue3::from((TestTokenIdentifier::new(SECOND_TEST_TOKEN), 100, 0)), - ], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ) -} - -/// Test that register token works with a valid prefix -#[test] -fn register_token_fungible_token_with_prefix() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(SOV_TOKEN); - let token_type = EsdtTokenType::Fungible; - let token_display_name = "TokenOne"; - let token_ticker = FIRST_TEST_TOKEN; - let num_decimals = 3; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token(register_token_args, egld_payment, None); - - // TODO: Add check for storage after callback issue is fixed -} - -/// Test that register token fails when token has no prefix -#[test] -fn register_token_fungible_token_no_prefix() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let sov_token_id = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let token_type = EsdtTokenType::Fungible; - let token_display_name = "TokenOne"; - let token_ticker = FIRST_TEST_TOKEN; - let num_decimals = 3; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - let register_token_args = RegisterTokenArgs { - sov_token_id: sov_token_id.into(), - token_type, - token_display_name, - token_ticker, - num_decimals, - }; - - state.register_token( - register_token_args, - egld_payment, - Some(CANNOT_REGISTER_TOKEN), - ); -} - -/// Test that register token fails if the token is already registered -#[test] -fn register_native_token_already_registered() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let token_display_name = "TokenOne"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token( - FIRST_TEST_TOKEN, - token_display_name, - egld_payment.clone(), - None, - ); - - // TODO: Add check for storage after callback issue is fixed - - state.register_native_token( - FIRST_TEST_TOKEN, - token_display_name, - egld_payment.clone(), - None, - // NOTE: Some(NATIVE_TOKEN_ALREADY_REGISTERED) when fix is here, - ); -} - -/// Test that register native works in the happy flow -#[test] -fn register_native_token() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let token_display_name = "TokenOne"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token( - FIRST_TEST_TOKEN, - token_display_name, - egld_payment.clone(), - None, - ); - - // TODO: Check storage -} - -/// Test that execute operation fails when the Mvx-ESDT-Safe address is not set in Header-Verifier contract -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Create the operation -/// 3. Create the hash of hashes -/// 4. Deploy the Header-Verifier smart contract -/// 5. Call the execute operation function -/// 6. Check the operation hash status -#[test] -fn execute_operation_no_esdt_safe_registered() { - let mut state = MvxEsdtSafeTestState::new(); - let config = OptionalValue::Some(EsdtSafeConfig::default_config()); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, config); - - let payment = OperationEsdtPayment::new( - TokenIdentifier::from(FIRST_TEST_TOKEN), - 0, - EsdtTokenData::default(), - ); - - let operation_data = OperationData::new(1, OWNER_ADDRESS.to_managed_address(), None); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![payment].into(), - operation_data, - ); - - let hash_of_hashes = state.get_operation_hash(&operation); - - state.common_setup.deploy_header_verifier(); - state.execute_operation( - &hash_of_hashes, - &operation, - Some(NO_ESDT_SAFE_ADDRESS), - None, - ); - - state - .common_setup - .check_operation_hash_status_is_empty(&hash_of_hashes); -} - -/// Test that execute operation works in the happy flow -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Create the operation -/// 3. Create the hash of hashes -/// 4. Deploy the Header-Verifier smart contract -/// 5. Deploy the Testing smart contract -/// 6. Set the Mvx-ESDT-Safe address in the Header-Verifier smart contract -/// 7. Call the register operation function -/// 8. Call the execute operation function -/// 9. Check the operation hash status -#[test] -fn execute_operation_success() { - let mut state = MvxEsdtSafeTestState::new(); - let config = OptionalValue::Some(EsdtSafeConfig::default_config()); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, config); - - let token_data = EsdtTokenData { - amount: BigUint::from(100u64), - ..Default::default() - }; - - let payment = OperationEsdtPayment::new(TokenIdentifier::from(FIRST_TEST_TOKEN), 0, token_data); - - let gas_limit = 1; - let function = ManagedBuffer::::from("hello"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); - - let transfer_data = TransferData::new(gas_limit, function, args); - - let operation_data = - OperationData::new(1, OWNER_ADDRESS.to_managed_address(), Some(transfer_data)); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![payment].into(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - state - .common_setup - .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); -} - -/// Test execute operation with native token happy flow -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Register the native token -/// 3. Create the operation -/// 4. Create the hash of hashes -/// 5. Deploy the Header-Verifier smart contract -/// 6. Deploy the Testing smart contract -/// 7. Set the Mvx-ESDT-Safe address in the Header-Verifier smart contract -/// 8. Call the register operation function -/// 9. Call the execute operation function -/// 10. Check the operation hash status -#[test] -fn execute_operation_with_native_token_success() { - let mut state = MvxEsdtSafeTestState::new(); - let config = EsdtSafeConfig::default_config(); - state.deploy_contract(HEADER_VERIFIER_ADDRESS, OptionalValue::Some(config)); - - let token_display_name = "TokenOne"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token(FIRST_TEST_TOKEN, token_display_name, egld_payment, None); - - let token_data = EsdtTokenData { - amount: BigUint::from(100u64), - ..Default::default() - }; - - let payment = OperationEsdtPayment::new(TokenIdentifier::from(FIRST_TEST_TOKEN), 0, token_data); - - let gas_limit = 1; - let function = ManagedBuffer::::from("hello"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); - - let transfer_data = TransferData::new(gas_limit, function, args); - - let operation_data = - OperationData::new(1, OWNER_ADDRESS.to_managed_address(), Some(transfer_data)); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![payment].into(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - state - .common_setup - .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0u64, - 0u64, - ))], - TESTING_SC_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); -} - -/// This test checks the succsesful flow of executing an `operation` with burn mechanism -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe SC with roles for the trusted token -/// 2. Create the `operation` -/// 3. Deploy the needed smart contract (Header-Verifier, Fee-Market with no fee and Testing SC) -/// 4. Set the Fee-Market address in Header-Verifier -/// 5. Register the `operation` -/// 6. Register the native token -/// 7. Set the bridging mechanism to burn&mint -/// 8. Execute the `operation` -/// 9. Check if the registered `operation` hash status is empty -/// 10. Check the balances for the owner, Mvx-ESDT-Safe and Testing SC -#[test] -fn execute_operation_burn_mechanism_without_deposit_cannot_subtract() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - let token_data = EsdtTokenData { - amount: BigUint::from(100u64), - ..Default::default() - }; - - let payment = - OperationEsdtPayment::new(TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]), 0, token_data); - - let operation_data = OperationData::new(1, OWNER_ADDRESS.to_managed_address(), None); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![payment].into(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - let token_display_name = "NativeToken"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token(TRUSTED_TOKEN_IDS[0], token_display_name, egld_payment, None); - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0u64, - 0u64, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0u64, - 0u64, - ))], - TESTING_SC_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); -} - -/// This test checks the succsesful flow of executing an `operation` with burn mechanism -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe SC with roles for the trusted token -/// 2. Create the `operation` -/// 3. Deploy the needed smart contract (Header-Verifier, Fee-Market with no fee and Testing SC) -/// 4. Set the Fee-Market address in Mvx-ESDT-Safe and Header-Verifier -/// 5. Deposit the `payment` -/// 6. Check for the deposit log -/// 7. Register the `operation` -/// 8. Check if the registered `operation` is not locked -/// 9. Set the briding mechanism to burn&mint -/// 10. Execute the `operation` -/// 11. Check the balances for the owner, Mvx-ESDT-Safe and Testing SC -/// 12. Check if the `operation` hash was removed from the Header-Verifier SC -#[test] -fn execute_operation_success_burn_mechanism() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - let token_data = EsdtTokenData { - amount: BigUint::from(100u64), - ..Default::default() - }; - - let payment = OperationEsdtPayment::new( - TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]), - 0, - token_data.clone(), - ); - - let operation_data = OperationData::new(1, OWNER_ADDRESS.to_managed_address(), None); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![payment.clone()].into(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::from(vec![payment]), - None, - Some("deposit"), - ); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - state - .common_setup - .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); - - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - let expected_amount_trusted_token = BigUint::from(ONE_HUNDRED_MILLION) - &token_data.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]), - &expected_amount_trusted_token, - ); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0u64, - 0u64, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - state - .common_setup - .check_deposited_tokens_amount(vec![(TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), 0)]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0u64, - 100u64, - ))], - TESTING_SC_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); -} - -/// This test checks the flow of multiple deposit and executes along side bridging mechanism -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe SC with roles for the trusted token -/// 2. Deploy the needed smart contract (Header-Verifier, Fee-Market with no fee and Testing SC) -/// 3. Set the Fee-Market address in Mvx-ESDT-Safe and Header-Verifier -/// 4. Deposit the `deposit_payment` to the `USER` -/// 5. Check for logs and esdt balance -/// 6. Switch the bridging mechanism to Burn&Mint for the trusted token -/// 7. Check for `deposited_tokens_amount` mapper and esdt balance -/// 8. Create the first `operation` -/// 9. Register the `operation` -/// 10. Execute the `operation` -/// 11. Check for `deposited_tokens_amount` mapper and esdt balance -/// 12. Second deposit of `deposit_payment` to the `USER` -/// 13. Check for logs, `deposited_tokens_amount` mapper and esdt balance -/// 14. Set bridging mechanism back to Lock&Send -/// 15. Check `deposited_tokens_amount` mapper and esdt balance -/// 16. Create the second `operation` -/// 17. Register the `operation` -/// 18. Execute the `operation` -/// 19. Check for `deposited_tokens_amount` mapper and esdt balance -/// 12. Third deposit of `deposit_payment` to the `USER` -/// 19. Check for logs, `deposited_tokens_amount` mapper and esdt balance -#[test] -fn deposit_execute_switch_mechanism() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - let trusted_token_id = TRUSTED_TOKEN_IDS[0]; - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.common_setup.deploy_fee_market(None); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let deposited_trusted_token_payment_amount = 1000u64; - let deposit_trusted_token_payment_token_data = EsdtTokenData { - amount: BigUint::from(deposited_trusted_token_payment_amount), - ..Default::default() - }; - let deposit_trusted_token_payment = OperationEsdtPayment::new( - TokenIdentifier::from(trusted_token_id), - 0, - deposit_trusted_token_payment_token_data, - ); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::from(vec![deposit_trusted_token_payment.clone()]), - None, - Some("deposit"), - ); - - state - .common_setup - .world - .check_account(ESDT_SAFE_ADDRESS) - .esdt_balance(TestTokenIdentifier::new(trusted_token_id), 1000); - - state.set_token_burn_mechanism(trusted_token_id, None); - - let mut expected_deposited_amount = deposited_trusted_token_payment_amount; - - state.common_setup.check_deposited_tokens_amount(vec![( - TestTokenIdentifier::new(trusted_token_id), - expected_deposited_amount, - )]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - 0u64, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - let execute_trusted_token_payment_amount = 500u64; - let execute_trusted_token_payment_token_data = EsdtTokenData { - amount: BigUint::from(execute_trusted_token_payment_amount), - ..Default::default() - }; - let execute_trusted_token_payment = OperationEsdtPayment::new( - TokenIdentifier::from(trusted_token_id), - 0, - execute_trusted_token_payment_token_data, - ); - let operation_one_data = OperationData::new(1, OWNER_ADDRESS.to_managed_address(), None); - let operation_one = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![execute_trusted_token_payment.clone()].into(), - operation_one_data, - ); - let operation_one_hash = state.get_operation_hash(&operation_one); - let hash_of_hashes_one = ManagedBuffer::new_from_bytes(&sha256(&operation_one_hash.to_vec())); - let operations_hashes_one = - MultiValueEncoded::from(ManagedVec::from(vec![operation_one_hash.clone()])); - - state.register_operation( - ManagedBuffer::new(), - &hash_of_hashes_one, - operations_hashes_one, - ); - - state.execute_operation( - &hash_of_hashes_one, - &operation_one, - None, - Some("executedBridgeOp"), - ); - - let mut expected_receiver_amount = execute_trusted_token_payment_amount; - expected_deposited_amount -= execute_trusted_token_payment_amount; - - state.common_setup.check_deposited_tokens_amount(vec![( - TestTokenIdentifier::new(trusted_token_id), - expected_deposited_amount, - )]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - 0u64, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::from(vec![deposit_trusted_token_payment.clone()]), - None, - Some("deposit"), - ); - - expected_deposited_amount += deposited_trusted_token_payment_amount; - - state.common_setup.check_deposited_tokens_amount(vec![( - TestTokenIdentifier::new(trusted_token_id), - expected_deposited_amount, - )]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - 0u64, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - state.set_token_lock_mechanism(trusted_token_id, None); - - state - .common_setup - .check_deposited_tokens_amount(vec![(TestTokenIdentifier::new(trusted_token_id), 0)]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - expected_deposited_amount, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - let operation_two_data = OperationData::new(2, OWNER_ADDRESS.to_managed_address(), None); - let operation_two = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - vec![execute_trusted_token_payment.clone()].into(), - operation_two_data, - ); - let operation_two_hash = state.get_operation_hash(&operation_two); - let hash_of_hashes_two = ManagedBuffer::new_from_bytes(&sha256(&operation_two_hash.to_vec())); - let operations_hashes_two = - MultiValueEncoded::from(ManagedVec::from(vec![operation_two_hash.clone()])); - - state.register_operation( - ManagedBuffer::new(), - &hash_of_hashes_two, - operations_hashes_two, - ); - - state.execute_operation( - &hash_of_hashes_two, - &operation_two, - None, - Some("executedBridgeOp"), - ); - - state - .common_setup - .check_deposited_tokens_amount(vec![(TestTokenIdentifier::new(trusted_token_id), 0)]); - - expected_receiver_amount += execute_trusted_token_payment_amount; - expected_deposited_amount -= execute_trusted_token_payment_amount; - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - expected_deposited_amount, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - expected_receiver_amount, - ))], - TESTING_SC_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::from(vec![deposit_trusted_token_payment]), - None, - Some("deposit"), - ); - - expected_deposited_amount += deposited_trusted_token_payment_amount; - - state - .common_setup - .check_deposited_tokens_amount(vec![(TestTokenIdentifier::new(trusted_token_id), 0)]); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - expected_deposited_amount, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(trusted_token_id), - 0, - expected_receiver_amount, - ))], - TESTING_SC_ADDRESS.to_managed_address(), - testing_sc::contract_obj, - ); -} - -/// This test checks the flow of executing an Operation with no payments -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe SC with the default config -/// 2. Registed the native token -/// 3. Create the `operation` -/// 4. Deploy the needed smart contract (Header-Verifier, Fee-Market with no fee and Testing SC) -/// 5. Register the `operation` -/// 6. Check if the registered `operation` is not locked -/// 7. Execute the `operation` -/// 8. Check the emited logs -/// 9. Check if the `operation` hash was removed from the Header-Verifier SC -#[test] -fn execute_operation_no_payments() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - let token_display_name = "TokenOne"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token(FIRST_TEST_TOKEN, token_display_name, egld_payment, None); - - let gas_limit = 1; - let function = ManagedBuffer::::from("hello"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); - - let transfer_data = TransferData::new(gas_limit, function, args); - - let operation_data = - OperationData::new(1, OWNER_ADDRESS.to_managed_address(), Some(transfer_data)); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - ManagedVec::new(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - state - .common_setup - .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); -} - -/// This test checks the flow of executing an Operation with no payments -/// which should emit a failed event -/// Steps for this test: -/// 1. Deploy the Mvx-ESDT-Safe SC with the default config -/// 2. Registed the native token -/// 3. Create the `operation` -/// 4. Deploy the needed smart contract (Header-Verifier, Fee-Market with no fee and Testing SC) -/// 5. Register the `operation` -/// 6. Check if the registered `operation` is not locked -/// 7. Execute the `operation` -/// 8. Check the emited logs -/// 9. Check if the `operation` hash was removed from the Header-Verifier SC -#[test] -fn execute_operation_no_payments_failed_event() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - let token_display_name = "TokenOne"; - let egld_payment = BigUint::from(DEFAULT_ISSUE_COST); - - state.register_native_token(FIRST_TEST_TOKEN, token_display_name, egld_payment, None); - - let gas_limit = 1; - let function = ManagedBuffer::::from("WRONG_ENDPOINT"); - let args = - ManagedVec::>::from(vec![ManagedBuffer::from("1")]); - - let transfer_data = TransferData::new(gas_limit, function, args); - - let operation_data = - OperationData::new(1, OWNER_ADDRESS.to_managed_address(), Some(transfer_data)); - - let operation = Operation::new( - TESTING_SC_ADDRESS.to_managed_address(), - ManagedVec::new(), - operation_data, - ); - - let operation_hash = state.get_operation_hash(&operation); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - state.common_setup.deploy_header_verifier(); - state.common_setup.deploy_testing_sc(); - state.set_esdt_safe_address_in_header_verifier(ESDT_SAFE_ADDRESS); - - let operations_hashes = MultiValueEncoded::from(ManagedVec::from(vec![operation_hash.clone()])); - - state - .common_setup - .deploy_chain_config(SovereignConfig::default_config()); - - state.register_operation(ManagedBuffer::new(), &hash_of_hashes, operations_hashes); - - state - .common_setup - .check_operation_hash_status(&operation_hash, OperationHashStatus::NotLocked); - - state.execute_operation(&hash_of_hashes, &operation, None, Some("executedBridgeOp")); - - state - .common_setup - .check_operation_hash_status_is_empty(&operation_hash); -} - -/// This Test checks the flow for setting the token burn mechanism without having roles -#[test] -fn set_token_burn_mechanism_no_roles() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract( - HEADER_VERIFIER_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); - - state.set_token_burn_mechanism("WEGLD", Some(MINT_AND_BURN_ROLES_NOT_FOUND)); -} - -/// This Test checks the flow setting the bridging mechanism for a untrusted token -#[test] -fn set_token_burn_mechanism_token_not_trusted() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - state.set_token_burn_mechanism(FIRST_TEST_TOKEN, Some(TOKEN_ID_IS_NOT_TRUSTED)); -} - -/// This Test checks the flow setting the bridging mechanism to burn&mint -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Set token burn mechanism for any trusted token -/// 3. Check sc storage and balance -#[test] -fn set_token_burn_mechanism() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - - state - .common_setup - .world - .query() - .to(ESDT_SAFE_ADDRESS) - .whitebox(mvx_esdt_safe::contract_obj, |sc| { - assert!(sc - .burn_mechanism_tokens() - .contains(&TokenIdentifier::from(TRUSTED_TOKEN_IDS[0]))) - }); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 0, - 0, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); -} - -/// This Test checks the flow setting the bridging mechanism to lock&send -/// Steps: -/// 1. Deploy the Mvx-ESDT-Safe smart contract -/// 2. Set token burn mechanism for any trusted token -/// 3. Set token lock mech -/// 3. Check sc storage and balance -#[test] -fn set_token_lock_mechanism() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - state.set_token_lock_mechanism(TRUSTED_TOKEN_IDS[0], None); - - state - .common_setup - .world - .query() - .to(ESDT_SAFE_ADDRESS) - .whitebox(mvx_esdt_safe::contract_obj, |sc| { - assert!(sc.burn_mechanism_tokens().is_empty()) - }); - - state.common_setup.check_sc_esdt_balance( - vec![MultiValue3::from(( - TestTokenIdentifier::new(TRUSTED_TOKEN_IDS[0]), - 100, - 0, - ))], - ESDT_SAFE_ADDRESS.to_managed_address(), - mvx_esdt_safe::contract_obj, - ); -} - -/// This Test checks the flow setting the bridging mechanism to burn&mint of a Sovereign token -#[test] -fn set_token_lock_mechanism_token_from_sovereign() { - let mut state = MvxEsdtSafeTestState::new(); - state.deploy_contract_with_roles(); - - state.set_token_burn_mechanism(TRUSTED_TOKEN_IDS[0], None); - - state - .common_setup - .world - .tx() - .from(OWNER_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .whitebox(mvx_esdt_safe::contract_obj, |sc| { - sc.multiversx_to_sovereign_token_id_mapper(&TokenIdentifier::from( - TRUSTED_TOKEN_IDS[0], - )) - .set(TokenIdentifier::from("MOCK")); - }); - - state.set_token_lock_mechanism(TRUSTED_TOKEN_IDS[0], Some(TOKEN_IS_FROM_SOVEREIGN)); -} diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.toml b/mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.toml deleted file mode 100644 index bbd04d74a..000000000 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "mvx-esdt-safe-full-wasm" -version = "0.0.0" -edition = "2021" -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.mvx-esdt-safe] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" - -[workspace] -members = ["."] diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.toml b/mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.toml deleted file mode 100644 index e9f0a25db..000000000 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "mvx-esdt-safe-view-wasm" -version = "0.0.0" -edition = "2021" -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.mvx-esdt-safe] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" - -[workspace] -members = ["."] diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/src/lib.rs b/mvx-esdt-safe/wasm-mvx-esdt-safe-view/src/lib.rs deleted file mode 100644 index 69bbe3bb0..000000000 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 0 -// Async Callback (empty): 1 -// Total number of exported functions: 2 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::external_view_init! {} - -multiversx_sc_wasm_adapter::external_view_endpoints! { - mvx_esdt_safe - ( - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe/src/lib.rs b/mvx-esdt-safe/wasm-mvx-esdt-safe/src/lib.rs deleted file mode 100644 index 7037b2d0a..000000000 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Upgrade: 1 -// Endpoints: 18 -// Async Callback (empty): 1 -// Promise callbacks: 3 -// Total number of exported functions: 24 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - mvx_esdt_safe - ( - init => init - upgrade => upgrade - updateConfiguration => update_configuration - setFeeMarketAddress => set_fee_market_address - setMaxBridgedAmount => set_max_bridged_amount - deposit => deposit - executeBridgeOps => execute_operations - registerToken => register_token - registerNativeToken => register_native_token - setTokenBurnMechanism => set_token_burn_mechanism - setTokenLockMechanism => set_token_lock_mechanism - getNativeToken => native_token - getMaxBridgedAmount => max_bridged_amount - pause => pause_endpoint - unpause => unpause_endpoint - isPaused => paused_status - isAdmin => is_admin - addAdmin => add_admin - removeAdmin => remove_admin - getAdmins => admins - execute => execute - issue_callback => issue_callback - native_token_issue_callback => native_token_issue_callback - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.lock b/mvx-esdt-safe/wasm/Cargo.lock similarity index 69% rename from mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.lock rename to mvx-esdt-safe/wasm/Cargo.lock index b5cd4aa73..1bd720214 100644 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.lock +++ b/mvx-esdt-safe/wasm/Cargo.lock @@ -10,56 +10,85 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "chain-config" version = "0.1.0" dependencies = [ + "common-utils", + "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", + "proxies", + "setup-phase", + "structs", +] + +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", ] [[package]] name = "cross-chain" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "fee-market" +name = "fee-common" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "proxies", "structs", - "utils", ] [[package]] @@ -75,10 +104,13 @@ dependencies = [ name = "header-verifier" version = "0.1.0" dependencies = [ + "common-utils", "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "proxies", + "setup-phase", "structs", ] @@ -90,15 +122,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -106,9 +138,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -122,9 +154,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -134,9 +166,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -146,9 +178,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -159,18 +191,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -180,17 +212,18 @@ name = "mvx-esdt-safe" version = "0.1.0" dependencies = [ "chain-config", + "common-utils", "cross-chain", + "custom-events", "error-messages", - "fee-market", "header-verifier", "multiversx-sc", "multiversx-sc-modules", + "mvx-fee-market", "proxies", "setup-phase", "structs", "testing-sc", - "utils", ] [[package]] @@ -201,6 +234,20 @@ dependencies = [ "mvx-esdt-safe", ] +[[package]] +name = "mvx-fee-market" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "proxies", + "setup-phase", + "structs", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -221,9 +268,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -238,18 +285,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -259,15 +306,17 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "structs" @@ -278,9 +327,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -292,6 +341,7 @@ name = "testing-sc" version = "0.1.0" dependencies = [ "multiversx-sc", + "proxies", ] [[package]] @@ -302,21 +352,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.toml b/mvx-esdt-safe/wasm/Cargo.toml similarity index 96% rename from mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.toml rename to mvx-esdt-safe/wasm/Cargo.toml index 7218ce67c..b38c3bbb9 100644 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe/Cargo.toml +++ b/mvx-esdt-safe/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/src/lib.rs b/mvx-esdt-safe/wasm/src/lib.rs similarity index 59% rename from mvx-esdt-safe/wasm-mvx-esdt-safe-full/src/lib.rs rename to mvx-esdt-safe/wasm/src/lib.rs index 7037b2d0a..1aec2e759 100644 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/src/lib.rs +++ b/mvx-esdt-safe/wasm/src/lib.rs @@ -6,10 +6,10 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 18 +// Endpoints: 26 // Async Callback (empty): 1 // Promise callbacks: 3 -// Total number of exported functions: 24 +// Total number of exported functions: 32 #![no_std] @@ -21,17 +21,25 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - updateConfiguration => update_configuration + updateEsdtSafeConfigSetupPhase => update_esdt_safe_config_during_setup_phase + updateEsdtSafeConfig => update_esdt_safe_config + pauseContract => switch_pause_status setFeeMarketAddress => set_fee_market_address - setMaxBridgedAmount => set_max_bridged_amount + completeSetupPhase => complete_setup_phase deposit => deposit executeBridgeOps => execute_operations - registerToken => register_token + registerToken => register_sovereign_token registerNativeToken => register_native_token + setTokenBurnMechanismSetupPhase => set_token_burn_mechanism_setup_phase setTokenBurnMechanism => set_token_burn_mechanism + setTokenLockMechanismSetupPhase => set_token_lock_mechanism_setup_phase setTokenLockMechanism => set_token_lock_mechanism + getDepositedTokensAmount => deposited_tokens_amount + getSovToMvxTokenId => sovereign_to_multiversx_token_id_mapper + getMvxToSovTokenId => multiversx_to_sovereign_token_id_mapper + getSovEsdtTokenInfo => sovereign_to_multiversx_esdt_info_mapper + getMvxEsdtTokenInfo => multiversx_to_sovereign_esdt_info_mapper getNativeToken => native_token - getMaxBridgedAmount => max_bridged_amount pause => pause_endpoint unpause => unpause_endpoint isPaused => paused_status @@ -40,7 +48,7 @@ multiversx_sc_wasm_adapter::endpoints! { removeAdmin => remove_admin getAdmins => admins execute => execute - issue_callback => issue_callback + register_token => register_token native_token_issue_callback => native_token_issue_callback ) } diff --git a/mvx-fee-market/Cargo.toml b/mvx-fee-market/Cargo.toml new file mode 100644 index 000000000..2c5b62274 --- /dev/null +++ b/mvx-fee-market/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "mvx-fee-market" +version = "0.1.0" +authors = ["you"] +edition = "2021" +publish = false + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.common-utils] +path = "../common/common-utils" + +[dependencies.setup-phase] +path = "../common/setup-phase" + +[dependencies.proxies] +path = "../common/proxies" + +[dependencies.structs] +path = "../common/structs" + +[dependencies.error-messages] +path = "../common/error-messages" + +[dependencies.custom-events] +path = "../common/custom-events" + +[dependencies.fee-common] +path = "../common/fee-common" + +[dev-dependencies] +num-bigint = "0.4.2" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.63.0" +features = ["bls"] + +[dev-dependencies.common-test-setup] +path = "../common/common-test-setup" diff --git a/mvx-fee-market/README.md b/mvx-fee-market/README.md new file mode 100644 index 000000000..db676235e --- /dev/null +++ b/mvx-fee-market/README.md @@ -0,0 +1,9 @@ +# MultiversX Fee Market + +Manages bridge fees and whitelists on the MultiversX side. After setup, fee changes must be authorized through the Header Verifier. + +- **Initialization:** `init(esdt_safe_address, fee)` stores the linked ESDT Safe and optional initial fee schedule. +- **Setup phase paths (owner-only):** `setFeeDuringSetupPhase`, `removeFeeDuringSetupPhase`, `addUsersToWhitelistSetupPhase`, `removeUsersFromWhitelistSetupPhase` allow configuring fees and whitelists before the bridge is opened. +- **Bridge-controlled paths:** `setFee`, `removeFee`, `distributeFees`, `addUsersToWhitelist`, `removeUsersFromWhitelist` are executed via signed operations. Each locks the operation hash in the Header Verifier before applying the change. +- **Completion:** `completeSetupPhase` marks the contract ready for bridge-controlled changes. +- **Interactions:** The ESDT Safe calls `subtractFee` (from shared endpoints) during deposits. The Header Verifier owns this contract after setup to gate operations. diff --git a/fee-market/meta/Cargo.toml b/mvx-fee-market/meta/Cargo.toml similarity index 63% rename from fee-market/meta/Cargo.toml rename to mvx-fee-market/meta/Cargo.toml index 2e66f391d..1b3271d51 100644 --- a/fee-market/meta/Cargo.toml +++ b/mvx-fee-market/meta/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "fee-market-meta" +name = "mvx-fee-market-meta" version = "0.1.0" edition = "2021" publish = false -[dependencies.fee-market] +[dependencies.mvx-fee-market] path = ".." [dependencies.multiversx-sc-meta-lib] -version = "=0.57.1" +version = "0.63.0" default-features = false diff --git a/mvx-fee-market/meta/src/main.rs b/mvx-fee-market/meta/src/main.rs new file mode 100644 index 000000000..4f8882991 --- /dev/null +++ b/mvx-fee-market/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/fee-market/multiversx.json b/mvx-fee-market/multiversx.json similarity index 100% rename from fee-market/multiversx.json rename to mvx-fee-market/multiversx.json diff --git a/mvx-fee-market/sc-config.toml b/mvx-fee-market/sc-config.toml new file mode 100644 index 000000000..994c9a956 --- /dev/null +++ b/mvx-fee-market/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "../common/proxies/src/mvx_fee_market_proxy.rs" diff --git a/mvx-fee-market/src/fee_operations.rs b/mvx-fee-market/src/fee_operations.rs new file mode 100644 index 000000000..2a3b7970a --- /dev/null +++ b/mvx-fee-market/src/fee_operations.rs @@ -0,0 +1,128 @@ +use error_messages::{SETUP_PHASE_ALREADY_COMPLETED, SETUP_PHASE_NOT_COMPLETED}; +use structs::{ + fee::{DistributeFeesOperation, FeeStruct, RemoveFeeOperation, SetFeeOperation}, + generate_hash::GenerateHash, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeOperationsModule: + setup_phase::SetupPhaseModule + + custom_events::CustomEventsModule + + common_utils::CommonUtilsModule + + fee_common::storage::FeeCommonStorageModule + + fee_common::helpers::FeeCommonHelpersModule + + fee_common::endpoints::FeeCommonEndpointsModule +{ + #[endpoint(distributeFees)] + fn distribute_fees( + &self, + hash_of_hashes: ManagedBuffer, + operation: DistributeFeesOperation, + ) { + let operation_hash = operation.generate_hash(); + if let Some(lock_operation_error) = + self.lock_operation_hash_wrapper(&hash_of_hashes, &operation_hash, operation.nonce) + { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + if let Some(err_msg) = self.validate_percentage_sum(&operation.pairs) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(err_msg)); + return; + } + + self.distribute_fees_and_reset(&operation.pairs); + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } + + #[only_owner] + #[endpoint(removeFeeDuringSetupPhase)] + fn remove_fee_during_setup_phase(&self, base_token: EgldOrEsdtTokenIdentifier) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + self.remove_fee_from_storage(&base_token); + } + + #[endpoint(removeFee)] + fn remove_fee( + &self, + hash_of_hashes: ManagedBuffer, + remove_fee_operation: RemoveFeeOperation, + ) { + let token_id_hash = remove_fee_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &token_id_hash, + remove_fee_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &token_id_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &token_id_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + self.remove_fee_from_storage(&remove_fee_operation.token_id); + self.complete_operation(&hash_of_hashes, &token_id_hash, None); + } + + #[only_owner] + #[endpoint(setFeeDuringSetupPhase)] + fn set_fee_during_setup_phase(&self, fee_struct: FeeStruct) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + if let Some(set_fee_error_msg) = self.set_fee_in_storage(&fee_struct) { + sc_panic!(set_fee_error_msg); + } + } + + #[endpoint(setFee)] + fn set_fee( + &self, + hash_of_hashes: ManagedBuffer, + set_fee_operation: SetFeeOperation, + ) { + let fee_hash = set_fee_operation.generate_hash(); + if let Some(lock_operation_error) = + self.lock_operation_hash_wrapper(&hash_of_hashes, &fee_hash, set_fee_operation.nonce) + { + self.complete_operation(&hash_of_hashes, &fee_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &fee_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + if let Some(set_fee_error_msg) = self.set_fee_in_storage(&set_fee_operation.fee_struct) { + self.complete_operation(&hash_of_hashes, &fee_hash, Some(set_fee_error_msg.into())); + return; + } + + self.complete_operation(&hash_of_hashes, &fee_hash, None); + } +} diff --git a/mvx-fee-market/src/fee_whitelist.rs b/mvx-fee-market/src/fee_whitelist.rs new file mode 100644 index 000000000..b28c5c578 --- /dev/null +++ b/mvx-fee-market/src/fee_whitelist.rs @@ -0,0 +1,102 @@ +use error_messages::{SETUP_PHASE_ALREADY_COMPLETED, SETUP_PHASE_NOT_COMPLETED}; +use structs::{ + fee::{AddUsersToWhitelistOperation, RemoveUsersFromWhitelistOperation}, + generate_hash::GenerateHash, +}; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeWhitelistModule: + fee_common::storage::FeeCommonStorageModule + + setup_phase::SetupPhaseModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule +{ + #[only_owner] + #[endpoint(addUsersToWhitelistSetupPhase)] + fn add_users_to_whitelist_during_setup_phase(&self, users: MultiValueEncoded) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + self.users_whitelist().extend(users); + } + + #[endpoint(addUsersToWhitelist)] + fn add_users_to_whitelist( + &self, + hash_of_hashes: ManagedBuffer, + add_to_whitelist_operation: AddUsersToWhitelistOperation, + ) { + let operation_hash = add_to_whitelist_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + add_to_whitelist_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + self.users_whitelist() + .extend(add_to_whitelist_operation.users); + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } + + #[only_owner] + #[endpoint(removeUsersFromWhitelistSetupPhase)] + fn remove_users_from_whitelist_during_setup_phase( + &self, + users: MultiValueEncoded, + ) { + require!( + !self.is_setup_phase_complete(), + SETUP_PHASE_ALREADY_COMPLETED + ); + + for user in users { + self.users_whitelist().swap_remove(&user); + } + } + + #[endpoint(removeUsersFromWhitelist)] + fn remove_users_from_whitelist( + &self, + hash_of_hashes: ManagedBuffer, + remove_from_whitelist_operation: RemoveUsersFromWhitelistOperation, + ) { + let operation_hash = remove_from_whitelist_operation.generate_hash(); + if let Some(lock_operation_error) = self.lock_operation_hash_wrapper( + &hash_of_hashes, + &operation_hash, + remove_from_whitelist_operation.nonce, + ) { + self.complete_operation(&hash_of_hashes, &operation_hash, Some(lock_operation_error)); + return; + } + if !self.is_setup_phase_complete() { + self.complete_operation( + &hash_of_hashes, + &operation_hash, + Some(SETUP_PHASE_NOT_COMPLETED.into()), + ); + return; + } + + for user in &remove_from_whitelist_operation.users { + self.users_whitelist().swap_remove(&user); + } + + self.complete_operation(&hash_of_hashes, &operation_hash, None); + } +} diff --git a/mvx-fee-market/src/lib.rs b/mvx-fee-market/src/lib.rs new file mode 100644 index 000000000..0af7c34fa --- /dev/null +++ b/mvx-fee-market/src/lib.rs @@ -0,0 +1,38 @@ +#![no_std] + +use structs::fee::FeeStruct; + +multiversx_sc::imports!(); + +pub mod fee_operations; +pub mod fee_whitelist; + +#[multiversx_sc::contract] +pub trait MvxFeeMarket: + common_utils::CommonUtilsModule + + setup_phase::SetupPhaseModule + + custom_events::CustomEventsModule + + fee_operations::FeeOperationsModule + + fee_common::storage::FeeCommonStorageModule + + fee_common::endpoints::FeeCommonEndpointsModule + + fee_common::helpers::FeeCommonHelpersModule + + fee_whitelist::FeeWhitelistModule +{ + #[init] + fn init(&self, esdt_safe_address: ManagedAddress, fee: Option>) { + self.init_fee_market(esdt_safe_address, fee); + } + + #[upgrade] + fn upgrade(&self) {} + + #[only_owner] + #[endpoint(completeSetupPhase)] + fn complete_setup_phase(&self) { + if self.is_setup_phase_complete() { + return; + } + + self.setup_phase_complete().set(true); + } +} diff --git a/mvx-fee-market/tests/mvx_fee_market_blackbox_setup.rs b/mvx-fee-market/tests/mvx_fee_market_blackbox_setup.rs new file mode 100644 index 000000000..7e5bab7a0 --- /dev/null +++ b/mvx-fee-market/tests/mvx_fee_market_blackbox_setup.rs @@ -0,0 +1,318 @@ +use common_test_setup::{ + base_setup::{init::ExpectedLogs, log_validations::assert_expected_logs}, + constants::{DISTRIBUTE_FEES_ENDPOINT, REMOVE_FEE_ENDPOINT}, +}; +use multiversx_sc::{ + imports::OptionalValue, + types::{ + Address, BigUint, EgldOrEsdtTokenIdentifier, EsdtTokenPayment, ManagedBuffer, ManagedVec, + MultiValueEncoded, ReturnsHandledOrError, TestAddress, TestTokenIdentifier, + }, +}; +use multiversx_sc_scenario::imports::*; + +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + constants::{ + CROWD_TOKEN_ID, ESDT_SAFE_ADDRESS, EXECUTED_BRIDGE_OP_EVENT, FEE_MARKET_ADDRESS, + FIRST_TEST_TOKEN, HEADER_VERIFIER_ADDRESS, MVX_ESDT_SAFE_CODE_PATH, OWNER_ADDRESS, + OWNER_BALANCE, SECOND_TEST_TOKEN, SET_FEE_ENDPOINT, USER_ADDRESS, + }, + log, +}; +use proxies::mvx_fee_market_proxy::MvxFeeMarketProxy; +use structs::fee::{ + AddUsersToWhitelistOperation, DistributeFeesOperation, FeeStruct, FeeType, RemoveFeeOperation, + RemoveUsersFromWhitelistOperation, SetFeeOperation, +}; + +pub struct MvxFeeMarketTestState { + pub common_setup: BaseSetup, +} + +pub enum WantedFeeType { + Correct, + InvalidToken, + LessThanFee, + None, + Fixed, +} + +impl MvxFeeMarketTestState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let owner_account = AccountSetup { + address: OWNER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE))]), + egld_balance: None, + }; + + let user_account = AccountSetup { + address: USER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE))]), + egld_balance: None, + }; + + let esdt_safe_address = AccountSetup { + address: ESDT_SAFE_ADDRESS.to_address(), + code_path: Some(MVX_ESDT_SAFE_CODE_PATH), + esdt_balances: Some(vec![ + (FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE)), + (SECOND_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE)), + (CROWD_TOKEN_ID, 0, BigUint::from(OWNER_BALANCE)), + ]), + egld_balance: None, + }; + + let account_setups = vec![owner_account, user_account, esdt_safe_address]; + + let common_setup = BaseSetup::new(account_setups); + + Self { common_setup } + } + + pub fn get_fee(&self) -> FeeStruct { + FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::from(100u64), + per_gas: BigUint::from(0u64), + }, + } + } + + pub fn subtract_fee( + &mut self, + payment_wanted: WantedFeeType, + original_caller: Address, + total_transfers: usize, + opt_gas_limit: OptionalValue, + expected_error_message: Option<&str>, + ) { + let payment: EsdtTokenPayment = match payment_wanted { + WantedFeeType::Correct => EsdtTokenPayment::new( + FIRST_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(200u64), + ), + WantedFeeType::InvalidToken => EsdtTokenPayment::new( + SECOND_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(10u64), + ), + WantedFeeType::LessThanFee => EsdtTokenPayment::new( + FIRST_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(0u64), + ), + _ => panic!("Invalid payment wanted type"), + }; + + let response = self + .common_setup + .world + .tx() + .from(ESDT_SAFE_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .subtract_fee(original_caller, total_transfers, opt_gas_limit) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn remove_fee_during_setup_phase(&mut self, base_token: TestTokenIdentifier) { + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .remove_fee_during_setup_phase(base_token) + .run(); + } + + pub fn remove_fee( + &mut self, + hash_of_hashes: &ManagedBuffer, + remove_fee_operation: RemoveFeeOperation, + expected_error_message: Option<&str>, + ) { + let (response, logs) = self + .common_setup + .world + .tx() + .from(HEADER_VERIFIER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .remove_fee(hash_of_hashes, remove_fee_operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + let expected_logs = vec![ + log!(REMOVE_FEE_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_error_message), + ]; + + self.common_setup + .assert_expected_error_message(response, None); + + assert_expected_logs(logs, expected_logs); + } + + pub fn set_fee( + &mut self, + hash_of_hashes: &ManagedBuffer, + set_fee_operation: SetFeeOperation, + expected_error_message: Option<&str>, + ) { + let (response, logs) = self + .common_setup + .world + .tx() + .from(HEADER_VERIFIER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .set_fee(hash_of_hashes, set_fee_operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + + let expected_logs = vec![ + log!(SET_FEE_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_error_message), + ]; + + assert_expected_logs(logs, expected_logs); + } + + pub fn set_fee_during_setup_phase( + &mut self, + token_id: EgldOrEsdtTokenIdentifier, + fee_type: WantedFeeType, + expected_error_message: Option<&str>, + ) { + let fee_struct: FeeStruct = match fee_type { + WantedFeeType::None => { + let fee_type = FeeType::None; + FeeStruct { + base_token: token_id.clone(), + fee_type, + } + } + WantedFeeType::Fixed => { + let fee_type = FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::from(10u8), + per_gas: BigUint::from(10u8), + }; + FeeStruct { + base_token: token_id, + fee_type, + } + } + _ => { + panic!("Invalid fee type"); + } + }; + + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .set_fee_during_setup_phase(fee_struct) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn distribute_fees( + &mut self, + hash_of_hashes: &ManagedBuffer, + operation: DistributeFeesOperation, + expected_error_message: Option<&str>, + ) { + let (response, logs) = self + .common_setup + .world + .tx() + .from(HEADER_VERIFIER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .distribute_fees(hash_of_hashes, operation) + .returns(ReturnsHandledOrError::new()) + .returns(ReturnsLogs) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + + let expected_logs = vec![ + log!(DISTRIBUTE_FEES_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: expected_error_message), + ]; + + assert_expected_logs(logs, expected_logs); + } + + pub fn add_users_to_whitelist_during_setup_phase(&mut self, users_vector: Vec) { + let mut users_vec = ManagedVec::new(); + + for user in users_vector { + users_vec.push(user.to_managed_address()); + } + + let users = MultiValueEncoded::from(users_vec); + + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .add_users_to_whitelist_during_setup_phase(users) + .run(); + } + + pub fn add_users_to_whitelist( + &mut self, + hash_of_hashes: &ManagedBuffer, + operation: AddUsersToWhitelistOperation, + ) { + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .add_users_to_whitelist(hash_of_hashes, operation) + .run(); + } + + pub fn remove_users_from_whitelist( + &mut self, + hash_of_hashes: &ManagedBuffer, + operation: RemoveUsersFromWhitelistOperation, + ) { + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(FEE_MARKET_ADDRESS) + .typed(MvxFeeMarketProxy) + .remove_users_from_whitelist(hash_of_hashes, operation) + .run(); + } +} diff --git a/mvx-fee-market/tests/mvx_fee_market_blackbox_test.rs b/mvx-fee-market/tests/mvx_fee_market_blackbox_test.rs new file mode 100644 index 000000000..4cba1ad1d --- /dev/null +++ b/mvx-fee-market/tests/mvx_fee_market_blackbox_test.rs @@ -0,0 +1,1013 @@ +use common_test_setup::base_setup::helpers::BLSKey; +use common_test_setup::constants::{ + ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FIRST_TEST_TOKEN, OWNER_ADDRESS, OWNER_BALANCE, + PER_TRANSFER, SECOND_TEST_TOKEN, USER_ADDRESS, WRONG_TOKEN_ID, +}; +use error_messages::{ + CURRENT_OPERATION_NOT_REGISTERED, INVALID_FEE, INVALID_FEE_TYPE, INVALID_PERCENTAGE_SUM, + INVALID_TOKEN_ID, PAYMENT_DOES_NOT_COVER_FEE, SETUP_PHASE_NOT_COMPLETED, + TOKEN_NOT_ACCEPTED_AS_FEE, +}; +use fee_common::storage::FeeCommonStorageModule; +use multiversx_sc::types::EgldOrEsdtTokenIdentifier; +use multiversx_sc::{ + imports::OptionalValue, + types::{BigUint, ManagedBuffer, ManagedVec, MultiEgldOrEsdtPayment, MultiValueEncoded}, +}; +use multiversx_sc_scenario::{ + api::StaticApi, multiversx_chain_vm::crypto_functions::sha256, ScenarioTxWhitebox, +}; +use structs::configs::SovereignConfig; +use structs::fee::{RemoveFeeOperation, SetFeeOperation}; +use structs::{ + fee::{ + AddUsersToWhitelistOperation, AddressPercentagePair, DistributeFeesOperation, FeeStruct, + FeeType, RemoveUsersFromWhitelistOperation, + }, + forge::ScArray, + generate_hash::GenerateHash, +}; + +use crate::mvx_fee_market_blackbox_setup::{MvxFeeMarketTestState, WantedFeeType}; + +mod mvx_fee_market_blackbox_setup; + +#[test] +fn test_deploy_fee_market() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); +} + +/// ### TEST +/// F-MARKET_SET_FEE_DURING_SETUP_PHASE_FAIL +/// +/// ### ACTION +/// Call 'set_fee_during_setup_phase()' with wrong parameters +/// +/// ### EXPECTED +/// Errors: INVALID_TOKEN_ID, INVALID_FEE_TYPE, INVALID_FEE +#[test] +fn test_set_fee_during_setup_phase_wrong_params() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.set_fee_during_setup_phase( + EgldOrEsdtTokenIdentifier::esdt(WRONG_TOKEN_ID), + WantedFeeType::Fixed, + Some(INVALID_TOKEN_ID), + ); + + state.set_fee_during_setup_phase( + EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + WantedFeeType::None, + Some(INVALID_FEE_TYPE), + ); + + state.set_fee_during_setup_phase( + EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN), + WantedFeeType::Fixed, + Some(INVALID_FEE), + ); +} + +/// ### TEST +/// F-MARKET_SET_FEE_FAIL +/// +/// ### ACTION +/// Call `set_fee()` when setup phase is not completed +/// +/// ### EXPECTED +/// Error CALLER_NOT_OWNER +#[test] +fn test_set_fee_setup_not_completed() { + let mut state = MvxFeeMarketTestState::new(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::FeeMarket, ScArray::ChainConfig]); + + let fee_struct = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }, + }; + + let set_fee_operation = SetFeeOperation { + fee_struct, + nonce: state.common_setup.next_operation_nonce(), + }; + + state.set_fee( + &ManagedBuffer::new(), + set_fee_operation, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// F-MARKET_REMOVE_USERS_FROM_WHITELIST_OK +/// +/// ### ACTION +/// Call `remove_users_from_whitelist` +/// +/// ### EXPECTED +/// SC whitelist is updated +#[test] +fn test_remove_users_from_whitelist() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let new_users = vec![ + USER_ADDRESS.to_managed_address(), + OWNER_ADDRESS.to_managed_address(), + ]; + + let operation_one = AddUsersToWhitelistOperation { + nonce: state.common_setup.next_operation_nonce(), + users: ManagedVec::from_iter(new_users.clone()), + }; + let operation_two = RemoveUsersFromWhitelistOperation { + users: ManagedVec::from_iter(new_users.clone()), + nonce: state.common_setup.next_operation_nonce(), + }; + + let operation_one_hash = operation_one.generate_hash(); + let mut aggregated_hashes = operation_one_hash.clone(); + let operation_two_hash = operation_two.generate_hash(); + aggregated_hashes.append(&operation_two_hash); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&aggregated_hashes.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::FeeMarket, ScArray::ChainConfig]); + + state.common_setup.complete_fee_market_setup_phase(); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![operation_one_hash, operation_two_hash]), + ); + state.add_users_to_whitelist(&hash_of_hashes, operation_one); + + state + .common_setup + .query_user_fee_whitelist(Some(&new_users)); + + state.remove_users_from_whitelist(&hash_of_hashes, operation_two); + + state + .common_setup + .query_user_fee_whitelist(Some(&new_users)); +} + +/// ### TEST +/// F-MARKET_SET_FEE_OK +/// +/// ### ACTION +/// Call `set_fee()` +/// +/// ### EXPECTED +/// Fee is set in contract's storage +#[test] +fn test_set_fee() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let fee_struct = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }, + }; + + let set_fee_operation = SetFeeOperation { + fee_struct, + nonce: state.common_setup.next_operation_nonce(), + }; + let fee_hash = set_fee_operation.generate_hash(); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&fee_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::FeeMarket, ScArray::ChainConfig]); + + state.common_setup.complete_fee_market_setup_phase(); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![fee_hash]), + ); + + state.set_fee(&hash_of_hashes, set_fee_operation, None); + + state + .common_setup + .world + .query() + .to(FEE_MARKET_ADDRESS) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(!sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); +} + +/// ### TEST +/// F-MARKET_REMOVE_FEE_FAIL +/// +/// ### ACTION +/// Call `remove_fee()` when setup was not completed +/// +/// ### EXPECTED +/// Error CALLER_NOT_OWNER +#[test] +fn test_remove_fee_setup_phase_not_completed() { + let mut state = MvxFeeMarketTestState::new(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::FeeMarket]); + + let remove_fee_operation = RemoveFeeOperation { + token_id: EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_str()), + nonce: state.common_setup.next_operation_nonce(), + }; + + state.remove_fee( + &ManagedBuffer::new(), + remove_fee_operation, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// F-MARKET_REMOVE_FEE_OK +/// +/// ### ACTION +/// Register `set_fee()` and `remove_fee()` separately and then call `remove_fee` +/// +/// ### EXPECTED +/// Fee is removed the contract's storage +#[test] +fn test_remove_fee_register_separate_operations() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let token = EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN); + + // Setup set_fee operation with validator 0 + let set_fee_operation = SetFeeOperation { + fee_struct: FeeStruct { + base_token: token.clone(), + fee_type: FeeType::Fixed { + token: token.clone(), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }, + }, + nonce: state.common_setup.next_operation_nonce(), + }; + let set_fee_hash = set_fee_operation.generate_hash(); + let set_fee_hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&set_fee_hash.to_vec())); + let (signature_set, pub_keys_set) = state + .common_setup + .get_sig_and_pub_keys(1, &set_fee_hash_of_hashes); + state.common_setup.register( + pub_keys_set.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + // Setup remove_fee operation with validator 1 + let remove_fee_operation = RemoveFeeOperation { + token_id: token.clone(), + nonce: state.common_setup.next_operation_nonce(), + }; + let remove_fee_hash = remove_fee_operation.generate_hash(); + let remove_fee_hash_of_hashes = + ManagedBuffer::new_from_bytes(&sha256(&remove_fee_hash.to_vec())); + let (signature_remove, pub_keys_remove) = state + .common_setup + .get_sig_and_pub_keys(1, &remove_fee_hash_of_hashes); + state.common_setup.register( + pub_keys_remove.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::FeeMarket]); + state.common_setup.complete_fee_market_setup_phase(); + state + .common_setup + .complete_header_verifier_setup_phase(None); + + // === Execute set_fee operation (validator 0) === + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_set, + &set_fee_hash_of_hashes, + state.common_setup.bitmap_for_signers(&[0]), + 0, + MultiValueEncoded::from_iter(vec![set_fee_hash]), + ); + + state.set_fee(&set_fee_hash_of_hashes, set_fee_operation, None); + + state + .common_setup + .world + .query() + .to(FEE_MARKET_ADDRESS) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(!sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); + + // === Execute remove_fee operation (validator 1) === + state.common_setup.register_operation( + OWNER_ADDRESS, + signature_remove, + &remove_fee_hash_of_hashes, + state.common_setup.bitmap_for_signers(&[1]), + 0, + MultiValueEncoded::from_iter(vec![remove_fee_hash]), + ); + + state.remove_fee(&remove_fee_hash_of_hashes, remove_fee_operation, None); + + state + .common_setup + .world + .query() + .to(FEE_MARKET_ADDRESS) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); +} + +/// ### TEST +/// F-MARKET_REMOVE_FEE_OK +/// +/// ### ACTION +/// Register both `set_fee()` and `remove_fee()` at the same time and then call `remove_fee` +/// +/// ### EXPECTED +/// Fee is removed the contract's storage +#[test] +fn test_remove_fee_register_with_one_hash_of_hashes() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let fee_struct = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }, + }; + + let set_fee_operation = SetFeeOperation { + fee_struct, + nonce: state.common_setup.next_operation_nonce(), + }; + + let remove_fee_operation = RemoveFeeOperation { + token_id: EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_str()), + nonce: state.common_setup.next_operation_nonce(), + }; + + let remove_fee_hash: ManagedBuffer = remove_fee_operation.generate_hash(); + let register_fee_hash = set_fee_operation.generate_hash(); + let mut aggregated_hashes = ManagedBuffer::new(); + + aggregated_hashes.append(&remove_fee_hash); + aggregated_hashes.append(®ister_fee_hash); + + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&aggregated_hashes.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::FeeMarket]); + + state.common_setup.complete_fee_market_setup_phase(); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![remove_fee_hash, register_fee_hash]), + ); + + state.set_fee(&hash_of_hashes, set_fee_operation, None); + + state + .common_setup + .world + .query() + .to(FEE_MARKET_ADDRESS) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(!sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); + + state.remove_fee(&hash_of_hashes, remove_fee_operation, None); + + state + .common_setup + .world + .query() + .to(FEE_MARKET_ADDRESS) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); +} + +/// ### TEST +/// F-MARKET_DISTRIBUTE_FEES_FAIL +/// +/// ### ACTION +/// Call 'distribute_fees()' when setup is not completed +/// +/// ### EXPECTED +/// Error CALLER_NOT_OWNER +#[test] +fn distribute_fees_setup_not_completed() { + let mut state = MvxFeeMarketTestState::new(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::FeeMarket]); + + let operation_nonce = state.common_setup.next_operation_nonce(); + let operation = DistributeFeesOperation { + pairs: ManagedVec::new(), + nonce: operation_nonce, + }; + state.distribute_fees( + &ManagedBuffer::new(), + operation, + Some(SETUP_PHASE_NOT_COMPLETED), + ); +} + +/// ### TEST +/// F-MARKET_DISTRIBUTE_FEES_FAIL +/// +/// ### ACTION +/// Call 'distribute_fees()' when operation is not registered +/// +/// ### EXPECTED +/// Error CURRENT_OPERATION_NOT_REGISTERED +#[test] +fn distribute_fees_operation_not_registered() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + state + .common_setup + .register(&BLSKey::random(), &MultiEgldOrEsdtPayment::new(), None); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state.common_setup.complete_fee_market_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::FeeMarket]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let operation_nonce = state.common_setup.next_operation_nonce(); + let operation = DistributeFeesOperation { + pairs: ManagedVec::new(), + nonce: operation_nonce, + }; + state.distribute_fees( + &ManagedBuffer::new(), + operation, + Some(CURRENT_OPERATION_NOT_REGISTERED), + ); +} + +/// ### TEST +/// F-MARKET_DISTRIBUTE_FEES_FAIL +/// +/// ### ACTION +/// Call 'distribute_fees()' with one pair +/// +/// ### EXPECTED +/// OWNER balance is unchanged, `failedBridgeOp` event emitted +#[test] +fn test_distribute_fees_percentage_under_limit() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let address_pair: AddressPercentagePair = AddressPercentagePair { + address: OWNER_ADDRESS.to_managed_address(), + percentage: 10, + }; + + let operation_nonce = state.common_setup.next_operation_nonce(); + let operation = DistributeFeesOperation { + pairs: ManagedVec::from_iter(vec![address_pair.clone()]), + nonce: operation_nonce, + }; + + let operation_hash = operation.generate_hash(); + let mut aggregated_hash: ManagedBuffer = ManagedBuffer::new(); + aggregated_hash.append(&operation_hash); + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&aggregated_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + + state.common_setup.complete_fee_market_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::FeeMarket]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![operation_hash]), + ); + + state.distribute_fees(&hash_of_hashes, operation, Some(INVALID_PERCENTAGE_SUM)); +} + +/// ### TEST +/// F-MARKET_DISTRIBUTE_FEES_OK +/// +/// ### ACTION +/// Call 'distribute_fees()' with one pair +/// +/// ### EXPECTED +/// OWNER balance is changed, `executedBridgeOp` event emitted +#[test] +fn test_distribute_fees() { + let mut state = MvxFeeMarketTestState::new(); + + state.common_setup.deploy_chain_config( + OptionalValue::Some(SovereignConfig::default_config_for_test()), + None, + ); + + let address_pair: AddressPercentagePair = AddressPercentagePair { + address: OWNER_ADDRESS.to_managed_address(), + percentage: 10_000, + }; + + let operation = DistributeFeesOperation { + pairs: ManagedVec::from_iter(vec![address_pair.clone()]), + nonce: state.common_setup.next_operation_nonce(), + }; + let operation_hash = operation.generate_hash(); + + let mut aggregated_hash: ManagedBuffer = ManagedBuffer::new(); + aggregated_hash.append(&operation_hash); + + let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&aggregated_hash.to_vec())); + + let (signature, public_keys) = state.common_setup.get_sig_and_pub_keys(1, &hash_of_hashes); + state.common_setup.register( + public_keys.first().unwrap(), + &MultiEgldOrEsdtPayment::new(), + None, + ); + + state.common_setup.complete_chain_config_setup_phase(); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: PER_TRANSFER.into(), + per_gas: BigUint::default(), + }, + }; + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + None, + ); + + state.common_setup.complete_fee_market_setup_phase(); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ChainConfig, ScArray::FeeMarket]); + + state + .common_setup + .complete_header_verifier_setup_phase(None); + + let bitmap = state.common_setup.full_bitmap(1); + let epoch = 0; + + state.common_setup.register_operation( + OWNER_ADDRESS, + signature, + &hash_of_hashes, + bitmap, + epoch, + MultiValueEncoded::from_iter(vec![operation_hash]), + ); + + state.distribute_fees(&hash_of_hashes, operation, None); + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0, + BigUint::from(OWNER_BALANCE) + PER_TRANSFER, + ); +} + +/// ### TEST +/// F-MARKET_SUBTRACT_FEE_OK +/// +/// ### ACTION +/// Call 'subtract_fee()' with no fee set +/// +/// ### EXPECTED +/// User balance is unchanged +#[test] +fn test_subtract_fee_no_fee() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.remove_fee_during_setup_phase(FIRST_TEST_TOKEN); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + None, + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); + + state.common_setup.check_account_single_esdt( + USER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); +} + +/// ### TEST +/// F-MARKET_SUBTRACT_FEE_OK +/// +/// ### ACTION +/// Call 'subtract_fee()' with a whitelisted user +/// +/// ### EXPECTED +/// User balance is unchanged +#[test] +fn test_subtract_fee_whitelisted() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + let whitelisted_users = vec![USER_ADDRESS]; + + state.add_users_to_whitelist_during_setup_phase(whitelisted_users); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + None, + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); + + state.common_setup.check_account_single_esdt( + USER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); +} + +/// ### TEST +/// F-MARKET_SUBTRACT_FEE_FAIL +/// +/// ### ACTION +/// Call 'subtract_fee()' with an invalid payment token +/// +/// ### EXPECTED +/// Error TOKEN_NOT_ACCEPTED_AS_FEE +#[test] +fn test_subtract_fee_invalid_payment_token() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::InvalidToken, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + Some(TOKEN_NOT_ACCEPTED_AS_FEE), + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); + + state.common_setup.check_account_single_esdt( + USER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); +} + +/// ### TEST +/// F-MARKET_SUBTRACT_FEE_FAIL +/// +/// ### ACTION +/// Call 'subtract_fee()' with not enough tokens to cover the fee +/// +/// ### EXPECTED +/// Error PAYMENT_DOES_NOT_COVER_FEE +#[test] +fn test_subtract_fixed_fee_payment_not_covered() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + state + .common_setup + .change_ownership_to_header_verifier(FEE_MARKET_ADDRESS); + + state.subtract_fee( + WantedFeeType::LessThanFee, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + Some(PAYMENT_DOES_NOT_COVER_FEE), + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); + + state.common_setup.check_account_single_esdt( + USER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE), + ); +} + +/// ### TEST +/// F-MARKET_SUBTRACT_FEE_OK +/// +/// ### ACTION +/// Call 'subtract_fee()' with payment bigger than fee +/// +/// ### EXPECTED +/// User balance is refunded with the difference +#[test] +fn test_subtract_fee_fixed_payment_bigger_than_fee() { + let mut state = MvxFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + state + .common_setup + .change_ownership_to_header_verifier(FEE_MARKET_ADDRESS); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1u64 as usize, + OptionalValue::Some(30u64), + None, + ); + + state.common_setup.check_account_single_esdt( + ESDT_SAFE_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE - 200), + ); + + state.common_setup.check_account_single_esdt( + USER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + BigUint::from(OWNER_BALANCE + 100), + ); +} diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.lock b/mvx-fee-market/wasm/Cargo.lock similarity index 71% rename from sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.lock rename to mvx-fee-market/wasm/Cargo.lock index 2593ec26d..57261e30a 100644 --- a/sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.lock +++ b/mvx-fee-market/wasm/Cargo.lock @@ -10,47 +10,57 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] -name = "cross-chain" +name = "common-utils" version = "0.1.0" dependencies = [ + "custom-events", "error-messages", "multiversx-sc", - "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "fee-market" +name = "fee-common" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "proxies", "structs", - "utils", ] [[package]] @@ -70,15 +80,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -86,9 +96,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -102,9 +112,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -114,9 +124,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -126,9 +136,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -139,22 +149,44 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] +[[package]] +name = "mvx-fee-market" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "proxies", + "setup-phase", + "structs", +] + +[[package]] +name = "mvx-fee-market-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "mvx-fee-market", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -175,9 +207,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -192,18 +224,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -213,39 +245,17 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "sov-esdt-safe" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "fee-market", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "setup-phase", - "structs", - "testing-sc", - "utils", -] - -[[package]] -name = "sov-esdt-safe-view-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "sov-esdt-safe", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "structs" @@ -256,22 +266,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "testing-sc" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - [[package]] name = "typenum" version = "1.18.0" @@ -280,21 +283,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/fee-market/wasm-fee-marker-full/Cargo.toml b/mvx-fee-market/wasm/Cargo.toml similarity index 87% rename from fee-market/wasm-fee-marker-full/Cargo.toml rename to mvx-fee-market/wasm/Cargo.toml index 3b2059790..afa778a19 100644 --- a/fee-market/wasm-fee-marker-full/Cargo.toml +++ b/mvx-fee-market/wasm/Cargo.toml @@ -5,7 +5,7 @@ # ########################################## [package] -name = "fee-marker-full-wasm" +name = "mvx-fee-market-wasm" version = "0.0.0" edition = "2021" publish = false @@ -24,11 +24,11 @@ overflow-checks = false [profile.dev] panic = "abort" -[dependencies.fee-market] +[dependencies.mvx-fee-market] path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/fee-market/wasm-fee-marker-full/src/lib.rs b/mvx-fee-market/wasm/src/lib.rs similarity index 67% rename from fee-market/wasm-fee-marker-full/src/lib.rs rename to mvx-fee-market/wasm/src/lib.rs index afd57dc7a..320f30081 100644 --- a/fee-market/wasm-fee-marker-full/src/lib.rs +++ b/mvx-fee-market/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 9 +// Endpoints: 13 // Async Callback (empty): 1 -// Total number of exported functions: 12 +// Total number of exported functions: 16 #![no_std] @@ -16,19 +16,23 @@ multiversx_sc_wasm_adapter::allocator!(); multiversx_sc_wasm_adapter::panic_handler!(); multiversx_sc_wasm_adapter::endpoints! { - fee_market + mvx_fee_market ( init => init upgrade => upgrade - setPriceAggregatorAddress => set_price_aggregator_address - setFee => set_fee + completeSetupPhase => complete_setup_phase + distributeFees => distribute_fees + removeFeeDuringSetupPhase => remove_fee_during_setup_phase removeFee => remove_fee + setFeeDuringSetupPhase => set_fee_during_setup_phase + setFee => set_fee getTokenFee => token_fee + getUsersWhitelist => users_whitelist + subtractFee => subtract_fee + addUsersToWhitelistSetupPhase => add_users_to_whitelist_during_setup_phase addUsersToWhitelist => add_users_to_whitelist + removeUsersFromWhitelistSetupPhase => remove_users_from_whitelist_during_setup_phase removeUsersFromWhitelist => remove_users_from_whitelist - distributeFees => distribute_fees - subtractFee => subtract_fee - getUsersWhitelist => users_whitelist ) } diff --git a/sov-esdt-safe/Cargo.toml b/sov-esdt-safe/Cargo.toml index ca1a14399..316a4d2ac 100644 --- a/sov-esdt-safe/Cargo.toml +++ b/sov-esdt-safe/Cargo.toml @@ -9,19 +9,22 @@ authors = ["you"] path = "src/lib.rs" [dependencies.multiversx-sc] -version = "0.57.1" +version = "0.63.0" [dependencies.multiversx-sc-modules] -version = "=0.57.1" +version = "0.63.0" [dependencies.testing-sc] path = "../testing-sc" -[dependencies.utils] -path = "../common/utils" +[dependencies.common-utils] +path = "../common/common-utils" -[dependencies.fee-market] -path = "../fee-market" +[dependencies.fee-common] +path = "../common/fee-common" + +[dependencies.mvx-fee-market] +path = "../mvx-fee-market" [dependencies.structs] path = "../common/structs" @@ -29,6 +32,12 @@ path = "../common/structs" [dependencies.cross-chain] path = "../common/cross-chain" +[dependencies.tx-nonce] +path = "../common/tx-nonce" + +[dependencies.custom-events] +path = "../common/custom-events" + [dependencies.setup-phase] path = "../common/setup-phase" @@ -42,4 +51,4 @@ path = "../common/error-messages" path = "../common/common-test-setup" [dev-dependencies.multiversx-sc-scenario] -version = "0.57.1" +version = "0.63.0" diff --git a/sov-esdt-safe/README.md b/sov-esdt-safe/README.md new file mode 100644 index 000000000..d32ad6a74 --- /dev/null +++ b/sov-esdt-safe/README.md @@ -0,0 +1,9 @@ +# Sovereign ESDT Safe + +Sovereign-side vault. It burns incoming assets to emit bridge events, lets admins adjust configuration, and anchors fee collection to the sovereign fee market. + +- **Initialization:** `init(fee_market_address, opt_config)` stores the linked fee market, applies config defaults if omitted, and starts paused. +- **Deposits (Sovereign → MultiversX):** `deposit(to, optTransferData)` burns the provided tokens and emits a deposit event containing the transfer data for validators to sign. +- **Registering assets:** `registerToken` issues a sovereign-wrapped token ID (requires the standard issue cost in EGLD). Tokens must carry the sovereign prefix. The call burns the payment and emits a registration event. +- **Configuration:** `updateConfiguration` updates the ESDT Safe config (whitelist/blacklist, gas limits, caps). `setFeeMarketAddress` retargets the fee sink. +- **Interactions:** The Fee Market on the sovereign side is the fee controller. Events emitted here are batched and signed by validators; matching operations are later executed on MultiversX by the Header Verifier + MultiversX ESDT Safe. diff --git a/sov-esdt-safe/meta/Cargo.toml b/sov-esdt-safe/meta/Cargo.toml index 7d10c24ab..f303a5320 100644 --- a/sov-esdt-safe/meta/Cargo.toml +++ b/sov-esdt-safe/meta/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "0.57.1" +version = "0.63.0" default-features = false diff --git a/sov-esdt-safe/sc-config.toml b/sov-esdt-safe/sc-config.toml index be57d0ac3..e337b44ee 100644 --- a/sov-esdt-safe/sc-config.toml +++ b/sov-esdt-safe/sc-config.toml @@ -1,16 +1,2 @@ -[contracts.main] -name = "sov-esdt-safe" - -[contracts.full] -name = "sov-esdt-safe-full" -add-unlabelled = true -add-labels = ["sov-esdt-safe-external-view"] - -[contracts.view] -name = "sov-esdt-safe-view" -external-view = true -add-unlabelled = false -add-labels = ["sov-esdt-safe-external-view"] - [[proxy]] path = "../common/proxies/src/sov_esdt_safe_proxy.rs" diff --git a/sov-esdt-safe/src/config_operations.rs b/sov-esdt-safe/src/config_operations.rs new file mode 100644 index 000000000..dfd66eac7 --- /dev/null +++ b/sov-esdt-safe/src/config_operations.rs @@ -0,0 +1,36 @@ +use structs::configs::{EsdtSafeConfig, SovereignConfig}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ConfigOperationsModule: + tx_nonce::TxNonceModule + + custom_events::CustomEventsModule + + fee_common::helpers::FeeCommonHelpersModule + + fee_common::storage::FeeCommonStorageModule + + common_utils::CommonUtilsModule +{ + #[only_owner] + #[endpoint(updateSovereignConfig)] + fn update_sovereign_config(&self, sovereign_config: SovereignConfig) { + self.update_sovereign_config_event(sovereign_config, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(updateEsdtSafeConfig)] + fn update_esdt_safe_config(&self, esdt_safe_config: EsdtSafeConfig) { + self.update_esdt_safe_config_event(esdt_safe_config, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(setTokenBurnMechanism)] + fn set_token_burn_mechanism(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.set_token_burn_mechanism_event(token_id, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(setTokenLockMechanism)] + fn set_token_lock_mechanism(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.set_token_lock_mechanism_event(token_id, self.get_and_save_next_tx_id()); + } +} diff --git a/sov-esdt-safe/src/deposit.rs b/sov-esdt-safe/src/deposit.rs index 36a1295c0..30f674a91 100644 --- a/sov-esdt-safe/src/deposit.rs +++ b/sov-esdt-safe/src/deposit.rs @@ -1,18 +1,14 @@ multiversx_sc::imports!(); -use error_messages::ESDT_SAFE_STILL_PAUSED; -use structs::{ - aliases::{EventPaymentTuple, OptionalValueTransferDataTuple}, - operation::{OperationData, TransferData}, -}; +use structs::aliases::{EventPaymentTuple, OptionalValueTransferDataTuple}; #[multiversx_sc::module] pub trait DepositModule: multiversx_sc_modules::pause::PauseModule - + utils::UtilsModule + + common_utils::CommonUtilsModule + cross_chain::deposit_common::DepositCommonModule + cross_chain::execute_common::ExecuteCommonModule + cross_chain::storage::CrossChainStorage - + cross_chain::events::EventsModule + + custom_events::CustomEventsModule { #[payable] #[endpoint] @@ -21,71 +17,17 @@ pub trait DepositModule: to: ManagedAddress, opt_transfer_data: OptionalValueTransferDataTuple, ) { - require!(self.not_paused(), ESDT_SAFE_STILL_PAUSED); - - let (fees_payment, payments) = self - .check_and_extract_fee(opt_transfer_data.is_some()) - .into_tuple(); - - let mut total_tokens_for_fees = 0usize; - let mut event_payments = MultiValueEncoded::new(); - let mut refundable_payments = ManagedVec::::new(); - let current_sc_address = self.blockchain().get_sc_address(); - - for payment in &payments { - // Process each payment and update the vectors accordingly. - if let Some(event_payment) = - self.process_payment(¤t_sc_address, &payment, &mut refundable_payments) - { - total_tokens_for_fees += 1; - event_payments.push(event_payment); - } - } - - let option_transfer_data = TransferData::from_optional_value(opt_transfer_data); - if let Some(transfer_data) = option_transfer_data.as_ref() { - self.require_gas_limit_under_limit(transfer_data.gas_limit); - self.require_endpoint_not_banned(&transfer_data.function); - } - - self.match_fee_payment(total_tokens_for_fees, &fees_payment, &option_transfer_data); - - let caller = self.blockchain().get_caller(); - self.refund_tokens(&caller, refundable_payments); - - let tx_nonce = self.get_and_save_next_tx_id(); - - if payments.is_empty() { - self.sc_call_event( - &to, - OperationData::new(tx_nonce, caller, option_transfer_data), - ); - - return; - } - self.deposit_event( - &to, - &event_payments, - OperationData::new(tx_nonce, caller, option_transfer_data), - ); + self.deposit_common(to, opt_transfer_data, |payment| { + self.process_payment(payment) + }); } fn process_payment( &self, - current_sc_address: &ManagedAddress, - payment: &EsdtTokenPayment, - refundable_payments: &mut ManagedVec>, - ) -> Option> { - self.require_below_max_amount(&payment.token_identifier, &payment.amount); - self.require_token_not_on_blacklist(&payment.token_identifier); - - if !self.is_token_whitelist_empty() && !self.is_token_whitelisted(&payment.token_identifier) - { - refundable_payments.push(payment.clone()); - return None; - } - + payment: &EgldOrEsdtTokenPayment, + ) -> EventPaymentTuple { + let current_sc_address = &self.blockchain().get_sc_address(); self.burn_sovereign_token(payment); - Some(self.get_event_payment_token_data(current_sc_address, payment)) + self.get_event_payment_token_data(current_sc_address, payment) } } diff --git a/sov-esdt-safe/src/fee_operations.rs b/sov-esdt-safe/src/fee_operations.rs new file mode 100644 index 000000000..cc431a124 --- /dev/null +++ b/sov-esdt-safe/src/fee_operations.rs @@ -0,0 +1,44 @@ +use structs::fee::{AddressPercentagePair, FeeStruct}; +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait FeeOperationsModule: + tx_nonce::TxNonceModule + + custom_events::CustomEventsModule + + fee_common::helpers::FeeCommonHelpersModule + + fee_common::storage::FeeCommonStorageModule + + common_utils::CommonUtilsModule +{ + #[only_owner] + #[endpoint(setFee)] + fn set_fee(&self, fee_struct: FeeStruct) { + self.set_fee_event(fee_struct, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(removeFee)] + fn remove_fee(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.remove_fee_event(token_id, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(distributeFees)] + fn distribute_fees( + &self, + address_percentage_pairs: ManagedVec>, + ) { + self.distribute_fees_event(address_percentage_pairs, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(addUsersToFeeWhitelist)] + fn add_users_to_fee_whitelist(&self, users: ManagedVec>) { + self.add_users_to_fee_whitelist_event(users, self.get_and_save_next_tx_id()); + } + + #[only_owner] + #[endpoint(removeUsersFromFeeWhitelist)] + fn remove_users_from_fee_whitelist(&self, users: ManagedVec>) { + self.remove_users_from_fee_whitelist_event(users, self.get_and_save_next_tx_id()); + } +} diff --git a/sov-esdt-safe/src/lib.rs b/sov-esdt-safe/src/lib.rs index 27cdb937a..ee766c6c4 100644 --- a/sov-esdt-safe/src/lib.rs +++ b/sov-esdt-safe/src/lib.rs @@ -1,10 +1,13 @@ #![no_std] - -#[allow(unused_imports)] +use cross_chain::DEFAULT_ISSUE_COST; +use error_messages::{EGLD_TOKEN_IDENTIFIER_EXPECTED, ISSUE_COST_NOT_COVERED, TOKEN_ID_NO_PREFIX}; +use multiversx_sc::api::ESDT_LOCAL_BURN_FUNC_NAME; use multiversx_sc::imports::*; -use structs::configs::EsdtSafeConfig; +use structs::{configs::EsdtSafeConfig, operation::OperationData}; +pub mod config_operations; pub mod deposit; +pub mod fee_operations; #[multiversx_sc::contract] pub trait SovEsdtSafe: @@ -13,8 +16,8 @@ pub trait SovEsdtSafe: + cross_chain::deposit_common::DepositCommonModule + cross_chain::execute_common::ExecuteCommonModule + cross_chain::storage::CrossChainStorage - + cross_chain::events::EventsModule - + utils::UtilsModule + + custom_events::CustomEventsModule + + common_utils::CommonUtilsModule + multiversx_sc_modules::pause::PauseModule { #[init] @@ -26,20 +29,62 @@ pub trait SovEsdtSafe: self.require_sc_address(&fee_market_address); self.fee_market_address().set(fee_market_address); - self.esdt_safe_config().set( - opt_config - .into_option() - .inspect(|config| self.require_esdt_config_valid(config)) - .unwrap_or_else(EsdtSafeConfig::default_config), - ); + let new_config = self.resolve_esdt_safe_config(opt_config); + + self.esdt_safe_config().set(new_config); self.set_paused(true); } + #[payable] + #[endpoint(registerToken)] + fn register_token( + &self, + token_id: EgldOrEsdtTokenIdentifier, + token_type: EsdtTokenType, + token_name: ManagedBuffer, + token_ticker: ManagedBuffer, + token_decimals: usize, + ) { + let call_value = self.call_value().egld_or_single_esdt().clone(); + require!( + call_value.token_identifier.is_egld(), + EGLD_TOKEN_IDENTIFIER_EXPECTED + ); + require!( + call_value.amount == DEFAULT_ISSUE_COST, + ISSUE_COST_NOT_COVERED + ); + require!(self.has_prefix(&token_id), TOKEN_ID_NO_PREFIX); + + self.tx() + .to(ToSelf) + .raw_call(ESDT_LOCAL_BURN_FUNC_NAME) + .argument(&call_value.token_identifier.as_managed_buffer()) + .argument(&call_value.amount) + .sync_call(); + + self.register_token_event( + token_id, + token_type, + token_name, + token_ticker, + token_decimals, + OperationData { + op_nonce: self.get_current_and_increment_tx_nonce(), + op_sender: self.blockchain().get_caller(), + opt_transfer_data: None, + }, + ); + } + #[only_owner] #[endpoint(updateConfiguration)] fn update_configuration(&self, new_config: EsdtSafeConfig) { - self.require_esdt_config_valid(&new_config); + if let Some(error_message) = self.is_esdt_safe_config_valid(&new_config) { + sc_panic!(error_message); + } + self.esdt_safe_config().set(new_config); } @@ -50,12 +95,6 @@ pub trait SovEsdtSafe: self.fee_market_address().set(fee_market_address); } - #[only_owner] - #[endpoint(setMaxBridgedAmount)] - fn set_max_bridged_amount(&self, token_id: TokenIdentifier, max_amount: BigUint) { - self.max_bridged_amount(&token_id).set(&max_amount); - } - #[upgrade] fn upgrade(&self) {} } diff --git a/sov-esdt-safe/tests/sov_esdt_safe_setup.rs b/sov-esdt-safe/tests/sov_esdt_safe_blackbox_setup.rs similarity index 62% rename from sov-esdt-safe/tests/sov_esdt_safe_setup.rs rename to sov-esdt-safe/tests/sov_esdt_safe_blackbox_setup.rs index 8e41d5656..2a9f2a123 100644 --- a/sov-esdt-safe/tests/sov_esdt_safe_setup.rs +++ b/sov-esdt-safe/tests/sov_esdt_safe_blackbox_setup.rs @@ -1,25 +1,33 @@ +use common_test_setup::{ + base_setup::init::ExpectedLogs, + constants::{REGISTER_TOKEN_ENDPOINT, SC_CALL_EVENT}, +}; use multiversx_sc::{ imports::OptionalValue, types::{ - EsdtLocalRole, ManagedAddress, ManagedVec, TestSCAddress, TestTokenIdentifier, - TokenIdentifier, + EsdtLocalRole, EsdtTokenIdentifier, ManagedAddress, ManagedVec, ReturnsHandledOrError, + TestSCAddress, }, }; -use multiversx_sc_scenario::{ - api::StaticApi, ReturnsHandledOrError, ReturnsLogs, ScenarioTxRun, ScenarioTxWhitebox, -}; +use multiversx_sc_scenario::imports::*; use common_test_setup::constants::{ - ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, ONE_HUNDRED_MILLION, - OWNER_ADDRESS, OWNER_BALANCE, SECOND_TEST_TOKEN, SOV_ESDT_SAFE_CODE_PATH, USER, + DEPOSIT_EVENT, ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, + ONE_HUNDRED_MILLION, OWNER_ADDRESS, OWNER_BALANCE, SECOND_TEST_TOKEN, SOV_ESDT_SAFE_CODE_PATH, + USER_ADDRESS, +}; +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + base_setup::log_validations::assert_expected_logs, + log, }; -use common_test_setup::{AccountSetup, BaseSetup}; use proxies::sov_esdt_safe_proxy::SovEsdtSafeProxy; use sov_esdt_safe::SovEsdtSafe; use structs::{ aliases::{OptionalValueTransferDataTuple, PaymentsVec}, configs::EsdtSafeConfig, + RegisterTokenStruct, }; pub struct SovEsdtSafeTestState { @@ -30,30 +38,20 @@ impl SovEsdtSafeTestState { #[allow(clippy::new_without_default)] pub fn new() -> Self { let owner_account = AccountSetup { - address: OWNER_ADDRESS, + address: OWNER_ADDRESS.to_address(), + code_path: None, esdt_balances: Some(vec![ - ( - TestTokenIdentifier::new(FIRST_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), - ( - TestTokenIdentifier::new(SECOND_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), - ( - TestTokenIdentifier::new(FEE_TOKEN), - ONE_HUNDRED_MILLION.into(), - ), + (FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), + (SECOND_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), + (FEE_TOKEN, 0u64, ONE_HUNDRED_MILLION.into()), ]), egld_balance: Some(OWNER_BALANCE.into()), }; let user_account = AccountSetup { - address: USER, - esdt_balances: Some(vec![( - TestTokenIdentifier::new(FIRST_TEST_TOKEN), - ONE_HUNDRED_MILLION.into(), - )]), + address: USER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0u64, ONE_HUNDRED_MILLION.into())]), egld_balance: Some(OWNER_BALANCE.into()), }; @@ -68,33 +66,6 @@ impl SovEsdtSafeTestState { Self { common_setup } } - pub fn deploy_contract( - &mut self, - fee_market_address: TestSCAddress, - opt_config: OptionalValue>, - ) -> &mut Self { - self.common_setup - .world - .tx() - .from(OWNER_ADDRESS) - .typed(SovEsdtSafeProxy) - .init(fee_market_address, opt_config) - .code(SOV_ESDT_SAFE_CODE_PATH) - .new_address(ESDT_SAFE_ADDRESS) - .run(); - - self.common_setup - .world - .tx() - .from(OWNER_ADDRESS) - .to(ESDT_SAFE_ADDRESS) - .typed(SovEsdtSafeProxy) - .unpause_endpoint() - .run(); - - self - } - pub fn deploy_contract_with_roles(&mut self) -> &mut Self { self.common_setup .world @@ -103,21 +74,21 @@ impl SovEsdtSafeTestState { .code(SOV_ESDT_SAFE_CODE_PATH) .owner(OWNER_ADDRESS) .esdt_roles( - TokenIdentifier::from(FIRST_TEST_TOKEN), + EsdtTokenIdentifier::from(FIRST_TEST_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), ], ) .esdt_roles( - TokenIdentifier::from(SECOND_TEST_TOKEN), + EsdtTokenIdentifier::from(SECOND_TEST_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), ], ) .esdt_roles( - TokenIdentifier::from(FEE_TOKEN), + EsdtTokenIdentifier::from(FEE_TOKEN), vec![ EsdtLocalRole::Burn.name().to_string(), EsdtLocalRole::NftBurn.name().to_string(), @@ -135,6 +106,8 @@ impl SovEsdtSafeTestState { ManagedVec::new(), 50_000_000, ManagedVec::new(), + ManagedVec::new(), + ManagedVec::new(), ); sc.init( @@ -161,7 +134,6 @@ impl SovEsdtSafeTestState { opt_transfer_data: OptionalValueTransferDataTuple, payment: PaymentsVec, expected_error_message: Option<&str>, - expected_custom_log: Option<&str>, ) { let (logs, response) = self .common_setup @@ -171,7 +143,7 @@ impl SovEsdtSafeTestState { .to(ESDT_SAFE_ADDRESS) .typed(SovEsdtSafeProxy) .deposit(to, opt_transfer_data.clone()) - .payment(payment) + .payment(payment.clone()) .returns(ReturnsLogs) .returns(ReturnsHandledOrError::new()) .run(); @@ -179,9 +151,14 @@ impl SovEsdtSafeTestState { self.common_setup .assert_expected_error_message(response, expected_error_message); - if let Some(custom_log) = expected_custom_log { - self.common_setup.assert_expected_log(logs, custom_log) - }; + if expected_error_message.is_none() { + let expected_logs = if payment.is_empty() { + vec![log!(DEPOSIT_EVENT, topics: [SC_CALL_EVENT])] + } else { + vec![log!(DEPOSIT_EVENT, topics: [DEPOSIT_EVENT])] + }; + assert_expected_logs(logs, expected_logs); + } } pub fn set_fee_market_address(&mut self, fee_market_address: TestSCAddress) { @@ -195,13 +172,11 @@ impl SovEsdtSafeTestState { .run(); } - pub fn deposit_with_logs( + pub fn register_token( &mut self, - to: ManagedAddress, - opt_transfer_data: OptionalValueTransferDataTuple, - payment: PaymentsVec, + new_token: RegisterTokenStruct, + payment: EgldOrEsdtTokenPayment, expected_error_message: Option<&str>, - expected_custom_log: Option<&str>, ) { let (logs, response) = self .common_setup @@ -210,7 +185,13 @@ impl SovEsdtSafeTestState { .from(OWNER_ADDRESS) .to(ESDT_SAFE_ADDRESS) .typed(SovEsdtSafeProxy) - .deposit(to, opt_transfer_data) + .register_token( + new_token.token_id, + new_token.token_type, + new_token.token_display_name, + new_token.token_ticker, + new_token.num_decimals, + ) .payment(payment) .returns(ReturnsLogs) .returns(ReturnsHandledOrError::new()) @@ -219,8 +200,11 @@ impl SovEsdtSafeTestState { self.common_setup .assert_expected_error_message(response, expected_error_message); - if let Some(custom_log) = expected_custom_log { - self.common_setup.assert_expected_log(logs, custom_log) - }; + if expected_error_message.is_none() { + let expected_logs = + vec![log!(REGISTER_TOKEN_ENDPOINT, topics: [REGISTER_TOKEN_ENDPOINT])]; + + assert_expected_logs(logs, expected_logs); + } } } diff --git a/sov-esdt-safe/tests/sov_esdt_safe_blackbox_tests.rs b/sov-esdt-safe/tests/sov_esdt_safe_blackbox_tests.rs new file mode 100644 index 000000000..96645108e --- /dev/null +++ b/sov-esdt-safe/tests/sov_esdt_safe_blackbox_tests.rs @@ -0,0 +1,601 @@ +use common_test_setup::constants::{ + ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, ISSUE_COST, + ONE_HUNDRED_MILLION, ONE_HUNDRED_THOUSAND, OWNER_ADDRESS, PER_GAS, PER_TRANSFER, + SECOND_TEST_TOKEN, SOV_TOKEN, TESTING_SC_ENDPOINT, USER_ADDRESS, +}; +use error_messages::{ + ACTION_IS_NOT_ALLOWED, EGLD_TOKEN_IDENTIFIER_EXPECTED, ISSUE_COST_NOT_COVERED, + NOTHING_TO_TRANSFER, TOKEN_ID_NO_PREFIX, +}; +use multiversx_sc::{ + chain_core::EGLD_000000_TOKEN_IDENTIFIER, + imports::{MultiValue3, OptionalValue}, + types::{ + BigUint, EgldOrEsdtTokenIdentifier, EgldOrEsdtTokenPayment, EsdtTokenPayment, + EsdtTokenType, ManagedBuffer, ManagedVec, MultiValueEncoded, + }, +}; +use multiversx_sc_scenario::api::StaticApi; +use sov_esdt_safe_blackbox_setup::SovEsdtSafeTestState; +use structs::{ + aliases::PaymentsVec, + configs::EsdtSafeConfig, + fee::{FeeStruct, FeeType}, + RegisterTokenStruct, +}; + +mod sov_esdt_safe_blackbox_setup; + +#[test] +fn test_deploy() { + let mut state = SovEsdtSafeTestState::new(); + + state.common_setup.deploy_sov_esdt_safe( + FEE_MARKET_ADDRESS, + OptionalValue::Some(EsdtSafeConfig::default_config()), + ); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'deposit()' with no transfer data and no fee +/// +/// ### EXPECTED +/// Deposit is executed successful +#[test] +fn test_deposit_no_fee_no_transfer_data() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + FIRST_TEST_TOKEN.into(), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + SECOND_TEST_TOKEN.into(), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![ + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec.clone(), + None, + ); + + let expected_tokens = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), expected_tokens); + + let expected_amount_token_one = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; + + let expected_amount_token_two = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; + + let expected_balances = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, expected_amount_token_one)), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, expected_amount_token_two)), + ]; + + state + .common_setup + .check_account_multiple_esdts(OWNER_ADDRESS.to_address(), expected_balances); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'deposit()' with no transfer data +/// +/// ### EXPECTED +/// Deposit is executed successful +#[test] +fn test_deposit_with_fee_no_transfer_data() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + + let fee_token_identifier = FEE_TOKEN; + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(fee_token_identifier), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(fee_token_identifier), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + + let fee_payment = + EsdtTokenPayment::::new(fee_token_identifier.into(), 0, fee_amount.clone()); + + let esdt_token_payment_one = + EsdtTokenPayment::::new(FIRST_TEST_TOKEN.into(), 0, BigUint::from(100u64)); + + let esdt_token_payment_two = + EsdtTokenPayment::::new(SECOND_TEST_TOKEN.into(), 0, BigUint::from(100u64)); + + let payments_vec = PaymentsVec::from(vec![ + fee_payment, + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + payments_vec.clone(), + None, + ); + + let expected_amount_token_one = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0, + expected_amount_token_one, + ); + + let expected_amount_token_two = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; + + let expected_tokens = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), expected_tokens); + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + SECOND_TEST_TOKEN, + 0u64, + expected_amount_token_two, + ); + + let expected_amount_token_fee = + BigUint::from(ONE_HUNDRED_MILLION) - BigUint::from(payments_vec.len() - 1) * PER_TRANSFER; + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + fee_token_identifier, + 0u64, + expected_amount_token_fee, + ); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and no fee +/// +/// ### EXPECTED +/// Deposit is executed successful +#[test] +fn test_deposit_no_fee_with_transfer_data() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let esdt_token_payment_one = EsdtTokenPayment::::new( + FIRST_TEST_TOKEN.into(), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let esdt_token_payment_two = EsdtTokenPayment::::new( + SECOND_TEST_TOKEN.into(), + 0, + BigUint::from(ONE_HUNDRED_THOUSAND), + ); + + let payments_vec = PaymentsVec::from(vec![ + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + let gas_limit = 1; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec.clone(), + None, + ); + + let expected_amount_token_one = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; + + let expected_tokens = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), expected_tokens); + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + expected_amount_token_one, + ); + + let expected_amount_token_two = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + SECOND_TEST_TOKEN, + 0u64, + expected_amount_token_two, + ); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and fee +/// +/// ### EXPECTED +/// Deposit is executed successful +#[test] +fn test_deposit_with_fee_with_transfer_data() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + + let fee_token_identifier = FEE_TOKEN; + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(fee_token_identifier), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(fee_token_identifier), + per_transfer: PER_TRANSFER.into(), + per_gas: PER_GAS.into(), + }, + }; + + state + .common_setup + .deploy_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); + + let fee_payment = + EsdtTokenPayment::::new(fee_token_identifier.into(), 0, fee_amount.clone()); + + let esdt_token_payment_one = + EsdtTokenPayment::::new(FIRST_TEST_TOKEN.into(), 0, BigUint::from(100u64)); + + let esdt_token_payment_two = + EsdtTokenPayment::::new(SECOND_TEST_TOKEN.into(), 0, BigUint::from(100u64)); + + let payments_vec = PaymentsVec::from(vec![ + fee_payment, + esdt_token_payment_one.clone(), + esdt_token_payment_two.clone(), + ]); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data), + payments_vec.clone(), + None, + ); + + let expected_amount_token_one = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + FIRST_TEST_TOKEN, + 0u64, + expected_amount_token_one, + ); + + let expected_amount_token_two = + BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; + + let expected_tokens = vec![ + MultiValue3::from((FIRST_TEST_TOKEN, 0u64, BigUint::zero())), + MultiValue3::from((SECOND_TEST_TOKEN, 0u64, BigUint::zero())), + ]; + + state + .common_setup + .check_account_multiple_esdts(ESDT_SAFE_ADDRESS.to_address(), expected_tokens); + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + SECOND_TEST_TOKEN, + 0u64, + expected_amount_token_two, + ); + + let expected_amount_token_fee = BigUint::from(ONE_HUNDRED_MILLION) + - BigUint::from(payments_vec.len() - 1) * PER_TRANSFER + - BigUint::from(gas_limit) * PER_GAS; + + state.common_setup.check_account_single_esdt( + OWNER_ADDRESS.to_address(), + fee_token_identifier, + 0u64, + expected_amount_token_fee, + ); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_FAIL +/// +/// ### ACTION +/// Call 'deposit()' with no transfer data and no payments +/// +/// ### EXPECTED +/// Error NOTHING_TO_TRANSFER +#[test] +fn test_deposit_no_transfer_data_no_payments() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::None, + PaymentsVec::new(), + Some(NOTHING_TO_TRANSFER), + ); +} + +/// ### TEST +/// S-ESDT_DEPOSIT_OK +/// +/// ### ACTION +/// Call 'deposit()' with transfer data and no payments +/// +/// ### EXPECTED +/// Deposit is executed successfully +#[test] +fn test_deposit_sc_call_only() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let gas_limit = 2; + let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); + let args = + MultiValueEncoded::>::from(ManagedVec::from(vec![ + ManagedBuffer::from("1"), + ])); + + let transfer_data = MultiValue3::from((gas_limit, function, args)); + + state.deposit( + USER_ADDRESS.to_managed_address(), + OptionalValue::Some(transfer_data.clone()), + PaymentsVec::new(), + None, + ); +} + +/// ### TEST +/// S-ESDT_REGISTER_TOKEN_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with not enough EGLD to cover issue cost +/// +/// ### EXPECTED +/// ISSUE_COST_NOT_COVERED +#[test] +fn test_register_token_not_enough_issue_cost() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let egld_token_payment = EgldOrEsdtTokenPayment::::new( + EGLD_000000_TOKEN_IDENTIFIER.into(), + 0, + BigUint::from(1u64), + ); + + let new_token = RegisterTokenStruct { + token_id: EgldOrEsdtTokenIdentifier::from(SOV_TOKEN.as_str()), + token_type: EsdtTokenType::Fungible, + token_display_name: ManagedBuffer::from("Test Token"), + token_ticker: ManagedBuffer::from("TST"), + num_decimals: 18, + }; + + state.register_token(new_token, egld_token_payment, Some(ISSUE_COST_NOT_COVERED)); +} + +/// ### TEST +/// S-ESDT_REGISTER_TOKEN_FAIL +/// +/// ### ACTION +/// Call 'register_token()' non sov token ID +/// +/// ### EXPECTED +/// TOKEN_ID_NO_PREFIX +#[test] +fn test_register_token_with_no_prefix() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let egld_token_payment = EgldOrEsdtTokenPayment::::new( + EGLD_000000_TOKEN_IDENTIFIER.into(), + 0, + ISSUE_COST.into(), + ); + + let new_token = RegisterTokenStruct { + token_id: EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_str()), + token_type: EsdtTokenType::Fungible, + token_display_name: ManagedBuffer::from("Test Token"), + token_ticker: ManagedBuffer::from("TST"), + num_decimals: 18, + }; + + state.register_token(new_token, egld_token_payment, Some(TOKEN_ID_NO_PREFIX)); +} + +/// ### TEST +/// S-ESDT_REGISTER_TOKEN_FAIL +/// +/// ### ACTION +/// Call 'register_token()' with wrong payment +/// +/// ### EXPECTED +/// EGLD_TOKEN_IDENTIFIER_EXPECTED +#[test] +fn test_register_token_wrong_payment() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let egld_token_payment = EgldOrEsdtTokenPayment::::new( + EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_str()), + 0, + BigUint::from(1u64), + ); + + let new_token = RegisterTokenStruct { + token_id: EgldOrEsdtTokenIdentifier::from(FIRST_TEST_TOKEN.as_str()), + token_type: EsdtTokenType::Fungible, + token_display_name: ManagedBuffer::from("Test Token"), + token_ticker: ManagedBuffer::from("TST"), + num_decimals: 18, + }; + + state.register_token( + new_token, + egld_token_payment, + Some(EGLD_TOKEN_IDENTIFIER_EXPECTED), + ); +} + +/// ### TEST +/// S-ESDT_REGISTER_TOKEN_OK +/// +/// ### ACTION +/// Call 'register_token()' +/// +/// ### EXPECTED +/// Event is emitted +#[test] +#[ignore = "Until the EGLD_000000_TOKEN_IDENTIFIER is considered an ESDT this will fail since the contract cannot burn any EGLD"] +fn test_register_token() { + let mut state = SovEsdtSafeTestState::new(); + + state.deploy_contract_with_roles(); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.common_setup.deploy_testing_sc(); + state.set_fee_market_address(FEE_MARKET_ADDRESS); + + let egld_token_payment = EgldOrEsdtTokenPayment::::new( + EGLD_000000_TOKEN_IDENTIFIER.into(), + 0, + ISSUE_COST.into(), + ); + + let new_token = RegisterTokenStruct { + token_id: EgldOrEsdtTokenIdentifier::from(SOV_TOKEN.as_str()), + token_type: EsdtTokenType::Fungible, + token_display_name: ManagedBuffer::from("Test Token"), + token_ticker: ManagedBuffer::from("TST"), + num_decimals: 18, + }; + + state.register_token(new_token, egld_token_payment, Some(ACTION_IS_NOT_ALLOWED)); +} diff --git a/sov-esdt-safe/tests/sov_esdt_safe_unit_tests.rs b/sov-esdt-safe/tests/sov_esdt_safe_unit_tests.rs deleted file mode 100644 index 7ad873b18..000000000 --- a/sov-esdt-safe/tests/sov_esdt_safe_unit_tests.rs +++ /dev/null @@ -1,471 +0,0 @@ -use common_test_setup::constants::{ - ESDT_SAFE_ADDRESS, FEE_MARKET_ADDRESS, FEE_TOKEN, FIRST_TEST_TOKEN, ONE_HUNDRED_MILLION, - ONE_HUNDRED_THOUSAND, OWNER_ADDRESS, SECOND_TEST_TOKEN, USER, -}; -use error_messages::NOTHING_TO_TRANSFER; -use multiversx_sc::{ - imports::{MultiValue3, OptionalValue}, - types::{ - BigUint, EsdtTokenPayment, ManagedBuffer, ManagedVec, MultiValueEncoded, - TestTokenIdentifier, TokenIdentifier, - }, -}; -use multiversx_sc_scenario::api::StaticApi; -use proxies::fee_market_proxy::{FeeStruct, FeeType}; -use sov_esdt_safe_setup::SovEsdtSafeTestState; -use structs::{aliases::PaymentsVec, configs::EsdtSafeConfig}; - -mod sov_esdt_safe_setup; - -#[test] -fn deploy() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract( - FEE_MARKET_ADDRESS, - OptionalValue::Some(EsdtSafeConfig::default_config()), - ); -} - -/// Test the deposit function without fee and without transfer data. -/// Steps: -/// 1. Deploy the Sov-ESDT-Safe smart contract with roles. -/// 2. Deploy the Fee-Market smart contract. -/// 3. Deploy the Testing smart contract. -/// 4. Set the Fee-Market address. -/// 5. Create two ESDT token payments. -/// 6. Create a payments vector with the two ESDT token payments. -/// 7. Call the deposit function with the payments vector. -/// 8. Check the logs for the deposit function. -/// 9. Check the ESDT balance of the addresses -#[test] -fn deposit_no_fee_no_transfer_data() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let test_token_one_identifier = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let test_token_two_identifier = TestTokenIdentifier::new(SECOND_TEST_TOKEN); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - test_token_one_identifier.into(), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - test_token_two_identifier.into(), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let payments_vec = PaymentsVec::from(vec![ - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - state.deposit_with_logs( - USER.to_managed_address(), - OptionalValue::None, - payments_vec.clone(), - None, - Some("deposit"), - ); - - state.common_setup.check_sc_esdt_balance( - vec![ - MultiValue3::from((test_token_one_identifier, 0u64, 0u64)), - MultiValue3::from((test_token_two_identifier, 0u64, 0u64)), - ], - ESDT_SAFE_ADDRESS.to_managed_address(), - sov_esdt_safe::contract_obj, - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_one_identifier, &expected_amount_token_one); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_two_identifier, &expected_amount_token_two); -} - -/// Test the deposit function with fee and without transfer data. -/// Steps: -/// 1. Deploy the Sov-ESDT-Safe smart contract with roles. -/// 2. Deploy the Fee-Market smart contract. -/// 3. Deploy the Testing smart contract. -/// 4. Set the Fee-Market address. -/// 5. Create a fee payment. -/// 6. Create two ESDT token payments. -/// 7. Create a payments vector with the fee payment and the two ESDT token payments. -/// 8. Call the deposit function with the payments vector. -/// 9. Check the ESDT balances of the addresses -#[test] -fn deposit_with_fee_no_transfer_data() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - let fee_token_identifier = TestTokenIdentifier::new(FEE_TOKEN); - - let fee = FeeStruct { - base_token: fee_token_identifier.into(), - fee_type: FeeType::Fixed { - token: fee_token_identifier.into(), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let test_token_one_identifier = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let test_token_two_identifier = TestTokenIdentifier::new(SECOND_TEST_TOKEN); - - let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); - - let fee_payment = - EsdtTokenPayment::::new(fee_token_identifier.into(), 0, fee_amount.clone()); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - test_token_one_identifier.into(), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - test_token_two_identifier.into(), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![ - fee_payment, - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - payments_vec.clone(), - None, - Some("deposit"), - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_one_identifier, expected_amount_token_one); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state.common_setup.check_sc_esdt_balance( - vec![ - MultiValue3::from((test_token_one_identifier, 0u64, 0u64)), - MultiValue3::from((test_token_two_identifier, 0u64, 0u64)), - ], - ESDT_SAFE_ADDRESS.to_managed_address(), - sov_esdt_safe::contract_obj, - ); - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_two_identifier, expected_amount_token_two); - - let expected_amount_token_fee = - BigUint::from(ONE_HUNDRED_MILLION) - BigUint::from(payments_vec.len() - 1) * per_transfer; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(fee_token_identifier, expected_amount_token_fee); -} - -/// Test the deposit function without fee and with transfer data. -/// Steps: -/// 1. Deploy the Sov-ESDT-Safe smart contract with roles. -/// 2. Deploy the Fee-Market smart contract. -/// 3. Deploy the Testing smart contract. -/// 4. Set the Fee-Market address. -/// 5. Create two ESDT token payments. -/// 6. Create a payments vector with the two ESDT token payments. -/// 7. Call the deposit function with the payments vector. -/// 8. Check the logs for the deposit function. -/// 9. Check the ESDT balance of the addresses -#[test] -fn deposit_no_fee_with_transfer_data() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let test_token_one_identifier = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let test_token_two_identifier = TestTokenIdentifier::new(SECOND_TEST_TOKEN); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - test_token_one_identifier.into(), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - test_token_two_identifier.into(), - 0, - BigUint::from(ONE_HUNDRED_THOUSAND), - ); - - let payments_vec = PaymentsVec::from(vec![ - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - let gas_limit = 1; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit_with_logs( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec.clone(), - None, - Some("deposit"), - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state.common_setup.check_sc_esdt_balance( - vec![ - MultiValue3::from((test_token_one_identifier, 0u64, 0u64)), - MultiValue3::from((test_token_two_identifier, 0u64, 0u64)), - ], - ESDT_SAFE_ADDRESS.to_managed_address(), - sov_esdt_safe::contract_obj, - ); - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_one_identifier, &expected_amount_token_one); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(SECOND_TEST_TOKEN), - &expected_amount_token_two, - ); -} - -/// Test the deposit function with fee and with transfer data. -/// Steps: -/// 1. Deploy the Sov-ESDT-Safe smart contract with roles. -/// 2. Deploy the Fee-Market smart contract. -/// 3. Deploy the Testing smart contract. -/// 4. Set the Fee-Market address. -/// 5. Create a fee payment. -/// 6. Create two ESDT token payments. -/// 7. Create a payments vector with the fee payment and the two ESDT token payments. -/// 8. Call the deposit function with the payments vector. -/// 9. Check the ESDT balances of the addresses -#[test] -fn deposit_with_fee_with_transfer_data() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - - let per_transfer = BigUint::from(100u64); - let per_gas = BigUint::from(1u64); - let fee_token_identifier = TestTokenIdentifier::new(FEE_TOKEN); - - let fee = FeeStruct { - base_token: fee_token_identifier.into(), - fee_type: FeeType::Fixed { - token: fee_token_identifier.into(), - per_transfer: per_transfer.clone(), - per_gas: per_gas.clone(), - }, - }; - - state.common_setup.deploy_fee_market(Some(fee)); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let test_token_one_identifier = TestTokenIdentifier::new(FIRST_TEST_TOKEN); - let test_token_two_identifier = TestTokenIdentifier::new(SECOND_TEST_TOKEN); - - let fee_amount = BigUint::from(ONE_HUNDRED_THOUSAND); - - let fee_payment = - EsdtTokenPayment::::new(fee_token_identifier.into(), 0, fee_amount.clone()); - - let esdt_token_payment_one = EsdtTokenPayment::::new( - test_token_one_identifier.into(), - 0, - BigUint::from(100u64), - ); - - let esdt_token_payment_two = EsdtTokenPayment::::new( - test_token_two_identifier.into(), - 0, - BigUint::from(100u64), - ); - - let payments_vec = PaymentsVec::from(vec![ - fee_payment, - esdt_token_payment_one.clone(), - esdt_token_payment_two.clone(), - ]); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data), - payments_vec.clone(), - None, - Some("deposit"), - ); - - let expected_amount_token_one = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_one.amount; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(test_token_one_identifier, expected_amount_token_one); - - let expected_amount_token_two = - BigUint::from(ONE_HUNDRED_MILLION) - &esdt_token_payment_two.amount; - - state.common_setup.check_sc_esdt_balance( - vec![ - MultiValue3::from((test_token_one_identifier, 0u64, 0u64)), - MultiValue3::from((test_token_two_identifier, 0u64, 0u64)), - ], - ESDT_SAFE_ADDRESS.to_managed_address(), - sov_esdt_safe::contract_obj, - ); - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance( - TokenIdentifier::from(SECOND_TEST_TOKEN), - expected_amount_token_two, - ); - - let expected_amount_token_fee = BigUint::from(ONE_HUNDRED_MILLION) - - BigUint::from(payments_vec.len() - 1) * per_transfer - - BigUint::from(gas_limit) * per_gas; - - state - .common_setup - .world - .check_account(OWNER_ADDRESS) - .esdt_balance(fee_token_identifier, expected_amount_token_fee); -} - -/// Test the deposit function with no transfer data and no payments -#[test] -fn deposit_no_transfer_data_no_payments() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - state.deposit( - USER.to_managed_address(), - OptionalValue::None, - PaymentsVec::new(), - Some(NOTHING_TO_TRANSFER), - None, - ); -} - -/// Test the deposit function with only cross-chain sc call -/// Steps: -/// 1. Deploy the Sov-ESDT-Safe smart contract with roles. -/// 2. Deploy the Fee-Market smart contract. -/// 3. Deploy the Testing smart contract. -/// 5. Set the Fee-Market address. -/// 6. Create the cross-chain sc call TransferData -/// 7. Check for the `scCall` log -#[test] -fn deposit_sc_call_only() { - let mut state = SovEsdtSafeTestState::new(); - - state.deploy_contract_with_roles(); - state.common_setup.deploy_fee_market(None); - state.common_setup.deploy_testing_sc(); - state.set_fee_market_address(FEE_MARKET_ADDRESS); - - let gas_limit = 2; - let function = ManagedBuffer::::from("hello"); - let args = - MultiValueEncoded::>::from(ManagedVec::from(vec![ - ManagedBuffer::from("1"), - ])); - - let transfer_data = MultiValue3::from((gas_limit, function, args)); - - state.deposit( - USER.to_managed_address(), - OptionalValue::Some(transfer_data.clone()), - PaymentsVec::new(), - None, - Some("scCall"), - ); -} diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.lock b/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.lock deleted file mode 100644 index 9a2bc7178..000000000 --- a/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.lock +++ /dev/null @@ -1,300 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "cross-chain" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "error-messages" -version = "0.1.0" - -[[package]] -name = "fee-market" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "proxies", - "structs", - "utils", -] - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-modules" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxies" -version = "0.1.0" -dependencies = [ - "multiversx-sc", - "structs", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "setup-phase" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "sov-esdt-safe" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "fee-market", - "multiversx-sc", - "multiversx-sc-modules", - "proxies", - "setup-phase", - "structs", - "testing-sc", - "utils", -] - -[[package]] -name = "sov-esdt-safe-full-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "sov-esdt-safe", -] - -[[package]] -name = "structs" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "testing-sc" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.toml b/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.toml deleted file mode 100644 index 5580b1c01..000000000 --- a/sov-esdt-safe/wasm-sov-esdt-safe-full/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "sov-esdt-safe-full-wasm" -version = "0.0.0" -edition = "2021" -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.sov-esdt-safe] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" - -[workspace] -members = ["."] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.toml b/sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.toml deleted file mode 100644 index cfd7d61fc..000000000 --- a/sov-esdt-safe/wasm-sov-esdt-safe-view/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "sov-esdt-safe-view-wasm" -version = "0.0.0" -edition = "2021" -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.sov-esdt-safe] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" - -[workspace] -members = ["."] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-view/src/lib.rs b/sov-esdt-safe/wasm-sov-esdt-safe-view/src/lib.rs deleted file mode 100644 index 0a31506f6..000000000 --- a/sov-esdt-safe/wasm-sov-esdt-safe-view/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 0 -// Async Callback (empty): 1 -// Total number of exported functions: 2 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::external_view_init! {} - -multiversx_sc_wasm_adapter::external_view_endpoints! { - sov_esdt_safe - ( - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/sov-esdt-safe/wasm-sov-esdt-safe/src/lib.rs b/sov-esdt-safe/wasm-sov-esdt-safe/src/lib.rs deleted file mode 100644 index 58e0580f6..000000000 --- a/sov-esdt-safe/wasm-sov-esdt-safe/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Upgrade: 1 -// Endpoints: 9 -// Async Callback (empty): 1 -// Total number of exported functions: 12 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - sov_esdt_safe - ( - init => init - upgrade => upgrade - updateConfiguration => update_configuration - setFeeMarketAddress => set_fee_market_address - setMaxBridgedAmount => set_max_bridged_amount - deposit => deposit - getNativeToken => native_token - getMaxBridgedAmount => max_bridged_amount - pause => pause_endpoint - unpause => unpause_endpoint - isPaused => paused_status - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.lock b/sov-esdt-safe/wasm/Cargo.lock similarity index 70% rename from mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.lock rename to sov-esdt-safe/wasm/Cargo.lock index 137d8c0ea..703eabebd 100644 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-view/Cargo.lock +++ b/sov-esdt-safe/wasm/Cargo.lock @@ -10,56 +10,70 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] -name = "chain-config" +name = "common-utils" version = "0.1.0" dependencies = [ + "custom-events", "error-messages", "multiversx-sc", - "multiversx-sc-modules", + "proxies", + "structs", ] [[package]] name = "cross-chain" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "fee-market" +name = "fee-common" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "proxies", "structs", - "utils", ] [[package]] @@ -71,17 +85,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "header-verifier" -version = "0.1.0" -dependencies = [ - "cross-chain", - "error-messages", - "multiversx-sc", - "proxies", - "structs", -] - [[package]] name = "hex" version = "0.4.3" @@ -90,15 +93,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -106,9 +109,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -122,9 +125,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -134,9 +137,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -146,9 +149,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -159,46 +162,34 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] [[package]] -name = "mvx-esdt-safe" +name = "mvx-fee-market" version = "0.1.0" dependencies = [ - "chain-config", - "cross-chain", + "common-utils", + "custom-events", "error-messages", - "fee-market", - "header-verifier", + "fee-common", "multiversx-sc", - "multiversx-sc-modules", "proxies", "setup-phase", "structs", - "testing-sc", - "utils", -] - -[[package]] -name = "mvx-esdt-safe-view-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "mvx-esdt-safe", ] [[package]] @@ -221,9 +212,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -238,18 +229,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -259,15 +250,44 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sov-esdt-safe" +version = "0.1.0" +dependencies = [ + "common-utils", + "cross-chain", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "multiversx-sc-modules", + "mvx-fee-market", + "proxies", + "setup-phase", + "structs", + "testing-sc", + "tx-nonce", +] + +[[package]] +name = "sov-esdt-safe-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "sov-esdt-safe", +] [[package]] name = "structs" @@ -278,9 +298,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -292,6 +312,15 @@ name = "testing-sc" version = "0.1.0" dependencies = [ "multiversx-sc", + "proxies", +] + +[[package]] +name = "tx-nonce" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "structs", ] [[package]] @@ -302,21 +331,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe/Cargo.toml b/sov-esdt-safe/wasm/Cargo.toml similarity index 96% rename from sov-esdt-safe/wasm-sov-esdt-safe/Cargo.toml rename to sov-esdt-safe/wasm/Cargo.toml index 05e2f19a0..944556456 100644 --- a/sov-esdt-safe/wasm-sov-esdt-safe/Cargo.toml +++ b/sov-esdt-safe/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/sov-esdt-safe/wasm-sov-esdt-safe-full/src/lib.rs b/sov-esdt-safe/wasm/src/lib.rs similarity index 68% rename from sov-esdt-safe/wasm-sov-esdt-safe-full/src/lib.rs rename to sov-esdt-safe/wasm/src/lib.rs index 58e0580f6..be438069b 100644 --- a/sov-esdt-safe/wasm-sov-esdt-safe-full/src/lib.rs +++ b/sov-esdt-safe/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 9 +// Endpoints: 12 // Async Callback (empty): 1 -// Total number of exported functions: 12 +// Total number of exported functions: 15 #![no_std] @@ -20,12 +20,15 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade + registerToken => register_token updateConfiguration => update_configuration setFeeMarketAddress => set_fee_market_address - setMaxBridgedAmount => set_max_bridged_amount deposit => deposit + getSovToMvxTokenId => sovereign_to_multiversx_token_id_mapper + getMvxToSovTokenId => multiversx_to_sovereign_token_id_mapper + getSovEsdtTokenInfo => sovereign_to_multiversx_esdt_info_mapper + getMvxEsdtTokenInfo => multiversx_to_sovereign_esdt_info_mapper getNativeToken => native_token - getMaxBridgedAmount => max_bridged_amount pause => pause_endpoint unpause => unpause_endpoint isPaused => paused_status diff --git a/fee-market/Cargo.toml b/sov-fee-market/Cargo.toml similarity index 55% rename from fee-market/Cargo.toml rename to sov-fee-market/Cargo.toml index 87f617a65..7c4143a0e 100644 --- a/fee-market/Cargo.toml +++ b/sov-fee-market/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fee-market" +name = "sov-fee-market" version = "0.1.0" authors = ["you"] edition = "2021" @@ -8,8 +8,11 @@ publish = false [lib] path = "src/lib.rs" -[dependencies.utils] -path = "../common/utils" +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.common-utils] +path = "../common/common-utils" [dependencies.proxies] path = "../common/proxies" @@ -17,14 +20,20 @@ path = "../common/proxies" [dependencies.structs] path = "../common/structs" -[dependencies.multiversx-sc] -version = "=0.57.1" - [dependencies.error-messages] path = "../common/error-messages" +[dependencies.custom-events] +path = "../common/custom-events" + +[dependencies.fee-common] +path = "../common/fee-common" + [dev-dependencies] num-bigint = "0.4.2" [dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" +version = "0.63.0" + +[dev-dependencies.common-test-setup] +path = "../common/common-test-setup" diff --git a/sov-fee-market/README.md b/sov-fee-market/README.md new file mode 100644 index 000000000..bab6da827 --- /dev/null +++ b/sov-fee-market/README.md @@ -0,0 +1,7 @@ +# Sovereign Fee Market + +Simple fee controller on the sovereign chain. Owners configure fees, distribute balances, and maintain a whitelist. + +- **Initialization:** `init(esdt_safe_address, fee)` links the sovereign ESDT Safe and optionally seeds an initial fee. +- **Operations (owner-only):** `setFee`, `removeFee`, `distributeFees`, `addUsersToWhitelist`, `removeUsersFromWhitelist`. +- **Interactions:** The sovereign ESDT Safe references this contract for fee lookups when processing deposits destined for MultiversX. diff --git a/sov-fee-market/meta/Cargo.toml b/sov-fee-market/meta/Cargo.toml new file mode 100644 index 000000000..5e9677fe1 --- /dev/null +++ b/sov-fee-market/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sov-fee-market-meta" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies.sov-fee-market] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +version = "0.63.0" +default-features = false diff --git a/sov-fee-market/meta/src/main.rs b/sov-fee-market/meta/src/main.rs new file mode 100644 index 000000000..341e25ba6 --- /dev/null +++ b/sov-fee-market/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/sov-fee-market/multiversx.json b/sov-fee-market/multiversx.json new file mode 100644 index 000000000..736553962 --- /dev/null +++ b/sov-fee-market/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/sov-fee-market/sc-config.toml b/sov-fee-market/sc-config.toml new file mode 100644 index 000000000..08680e164 --- /dev/null +++ b/sov-fee-market/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "../common/proxies/src/sov_fee_market_proxy.rs" diff --git a/sov-fee-market/src/fee_operations.rs b/sov-fee-market/src/fee_operations.rs new file mode 100644 index 000000000..b70408685 --- /dev/null +++ b/sov-fee-market/src/fee_operations.rs @@ -0,0 +1,41 @@ +use structs::fee::FeeStruct; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeOperationsModule: + custom_events::CustomEventsModule + + common_utils::CommonUtilsModule + + fee_common::storage::FeeCommonStorageModule + + fee_common::helpers::FeeCommonHelpersModule + + fee_common::endpoints::FeeCommonEndpointsModule +{ + #[only_owner] + #[endpoint(distributeFees)] + fn distribute_fees( + &self, + address_percentage_pairs: MultiValueEncoded>, + ) { + let pairs = self.parse_pairs(address_percentage_pairs); + if let Some(percentage_validation_err) = self.validate_percentage_sum(&pairs) { + sc_panic!(percentage_validation_err); + } + + self.distribute_fees_and_reset(&pairs); + } + + #[only_owner] + #[endpoint(removeFee)] + fn remove_fee(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.remove_fee_from_storage(&token_id); + } + + #[only_owner] + #[endpoint(setFee)] + fn set_fee(&self, fee_struct: FeeStruct) { + if let Some(set_fee_error_msg) = self.set_fee_in_storage(&fee_struct) { + sc_panic!(set_fee_error_msg); + } + } +} diff --git a/sov-fee-market/src/fee_whitelist.rs b/sov-fee-market/src/fee_whitelist.rs new file mode 100644 index 000000000..b585b32b3 --- /dev/null +++ b/sov-fee-market/src/fee_whitelist.rs @@ -0,0 +1,22 @@ +use error_messages::ITEM_NOT_IN_LIST; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[multiversx_sc::module] +pub trait FeeWhitelistModule: fee_common::storage::FeeCommonStorageModule { + #[only_owner] + #[endpoint(addUsersToWhitelist)] + fn add_users_to_whitelist(&self, users: MultiValueEncoded) { + self.users_whitelist().extend(users); + } + + #[only_owner] + #[endpoint(removeUsersFromWhitelist)] + fn remove_users_from_whitelist(&self, users: MultiValueEncoded) { + for user in users { + let was_removed = self.users_whitelist().swap_remove(&user); + require!(was_removed, ITEM_NOT_IN_LIST); + } + } +} diff --git a/sov-fee-market/src/lib.rs b/sov-fee-market/src/lib.rs new file mode 100644 index 000000000..c7213671b --- /dev/null +++ b/sov-fee-market/src/lib.rs @@ -0,0 +1,26 @@ +#![no_std] + +use fee_common::storage; +use multiversx_sc::imports::*; +use structs::fee::FeeStruct; +pub mod fee_operations; +pub mod fee_whitelist; + +#[multiversx_sc::contract] +pub trait SovFeeMarket: + fee_whitelist::FeeWhitelistModule + + storage::FeeCommonStorageModule + + fee_operations::FeeOperationsModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + fee_common::endpoints::FeeCommonEndpointsModule + + fee_common::helpers::FeeCommonHelpersModule +{ + #[init] + fn init(&self, esdt_safe_address: ManagedAddress, fee: Option>) { + self.init_fee_market(esdt_safe_address, fee); + } + + #[upgrade] + fn upgrade(&self) {} +} diff --git a/sov-fee-market/tests/sov_fee_market_blackbox_setup.rs b/sov-fee-market/tests/sov_fee_market_blackbox_setup.rs new file mode 100644 index 000000000..3776e994e --- /dev/null +++ b/sov-fee-market/tests/sov_fee_market_blackbox_setup.rs @@ -0,0 +1,246 @@ +use fee_common::storage::FeeCommonStorageModule; +use multiversx_sc::{ + imports::OptionalValue, + types::{ + Address, BigUint, EgldOrEsdtTokenIdentifier, EsdtTokenPayment, ReturnsHandledOrError, + TestTokenIdentifier, + }, +}; +use multiversx_sc_scenario::imports::*; + +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + constants::{ + CROWD_TOKEN_ID, ESDT_SAFE_ADDRESS, FIRST_TEST_TOKEN, MVX_ESDT_SAFE_CODE_PATH, + OWNER_ADDRESS, OWNER_BALANCE, PER_GAS, PER_TRANSFER, SECOND_TEST_TOKEN, + SOV_FEE_MARKET_ADDRESS, USER_ADDRESS, + }, +}; +use proxies::sov_fee_market_proxy::SovFeeMarketProxy; +use structs::fee::{FeeStruct, FeeType}; + +pub struct SovFeeMarketTestState { + pub common_setup: BaseSetup, +} + +pub enum WantedFeeType { + Correct, + InvalidToken, + LessThanFee, +} + +impl SovFeeMarketTestState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let owner_account = AccountSetup { + address: OWNER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE))]), + egld_balance: None, + }; + + let user_account = AccountSetup { + address: USER_ADDRESS.to_address(), + code_path: None, + esdt_balances: Some(vec![(FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE))]), + egld_balance: None, + }; + + let esdt_safe_address = AccountSetup { + address: ESDT_SAFE_ADDRESS.to_address(), + code_path: Some(MVX_ESDT_SAFE_CODE_PATH), + esdt_balances: Some(vec![ + (FIRST_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE)), + (SECOND_TEST_TOKEN, 0, BigUint::from(OWNER_BALANCE)), + (CROWD_TOKEN_ID, 0, BigUint::from(OWNER_BALANCE)), + ]), + egld_balance: None, + }; + + let account_setups = vec![owner_account, user_account, esdt_safe_address]; + + let common_setup = BaseSetup::new(account_setups); + + Self { common_setup } + } + + pub fn get_fee(&self) -> FeeStruct { + FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::from(PER_TRANSFER), + per_gas: BigUint::from(PER_GAS), + }, + } + } + + pub fn subtract_fee( + &mut self, + payment_wanted: WantedFeeType, + original_caller: Address, + total_transfers: usize, + opt_gas_limit: OptionalValue, + expected_error_message: Option<&str>, + ) { + let payment: EsdtTokenPayment = match payment_wanted { + WantedFeeType::Correct => EsdtTokenPayment::new( + FIRST_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(200u64), + ), + WantedFeeType::InvalidToken => EsdtTokenPayment::new( + SECOND_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(10u64), + ), + WantedFeeType::LessThanFee => EsdtTokenPayment::new( + FIRST_TEST_TOKEN.to_token_identifier(), + 0u64, + BigUint::from(0u64), + ), + }; + + let response = self + .common_setup + .world + .tx() + .from(ESDT_SAFE_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .subtract_fee(original_caller, total_transfers, opt_gas_limit) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn remove_fee( + &mut self, + token_id: TestTokenIdentifier, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .remove_fee(token_id.to_token_identifier()) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn set_fee( + &mut self, + fee_struct: &FeeStruct, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .set_fee(fee_struct) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn distribute_fees( + &mut self, + address_percentage_pairs: Vec, usize>>, + expected_error_message: Option<&str>, + ) { + let mut pairs = MultiValueEncoded::new(); + for pair in address_percentage_pairs { + pairs.push(pair); + } + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .distribute_fees(pairs) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn add_users_to_whitelist( + &mut self, + users: Vec>, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .add_users_to_whitelist(MultiValueEncoded::from(ManagedVec::from(users))) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn remove_users_from_whitelist( + &mut self, + users: Vec>, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOV_FEE_MARKET_ADDRESS) + .typed(SovFeeMarketProxy) + .remove_users_from_whitelist(MultiValueEncoded::from(ManagedVec::from(users))) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn check_token_fee_storage_is_empty(&mut self, token_id: TestTokenIdentifier) { + self.common_setup + .world + .query() + .to(SOV_FEE_MARKET_ADDRESS) + .whitebox(sov_fee_market::contract_obj, |sc| { + let token_fee = sc.token_fee(&EgldOrEsdtTokenIdentifier::esdt(token_id)); + assert!(token_fee.is_empty()); + }); + } + + pub fn check_accumulated_fees(&mut self, token_id: TestTokenIdentifier, amount: u64) { + self.common_setup + .world + .query() + .to(SOV_FEE_MARKET_ADDRESS) + .whitebox(sov_fee_market::contract_obj, |sc| { + let accumulated_fees = + sc.accumulated_fees(&EgldOrEsdtTokenIdentifier::esdt(token_id).unwrap_esdt()); + assert_eq!(accumulated_fees.get(), BigUint::from(amount)); + }); + } +} diff --git a/sov-fee-market/tests/sov_fee_market_blackbox_tests.rs b/sov-fee-market/tests/sov_fee_market_blackbox_tests.rs new file mode 100644 index 000000000..54977c52f --- /dev/null +++ b/sov-fee-market/tests/sov_fee_market_blackbox_tests.rs @@ -0,0 +1,437 @@ +use common_test_setup::constants::{ + ESDT_SAFE_ADDRESS, FIRST_TEST_TOKEN, OWNER_ADDRESS, PER_GAS, PER_TRANSFER, SECOND_TEST_TOKEN, + SOV_FEE_MARKET_ADDRESS, USER_ADDRESS, WRONG_TOKEN_ID, +}; +use error_messages::{ + INVALID_FEE, INVALID_FEE_TYPE, INVALID_PERCENTAGE_SUM, INVALID_TOKEN_ID, ITEM_NOT_IN_LIST, + PAYMENT_DOES_NOT_COVER_FEE, TOKEN_NOT_ACCEPTED_AS_FEE, +}; +use fee_common::storage::FeeCommonStorageModule; +use multiversx_sc::{ + imports::{MultiValue2, OptionalValue}, + types::{BigUint, EgldOrEsdtTokenIdentifier}, +}; +use multiversx_sc_scenario::ScenarioTxWhitebox; +use sov_fee_market_blackbox_setup::{SovFeeMarketTestState, WantedFeeType}; +use structs::fee::{FeeStruct, FeeType}; + +mod sov_fee_market_blackbox_setup; + +/// ### TEST +/// S-FEE-MARKET_DEPLOY_OK +/// +/// ### ACTION +/// Deploy sov-fee-market with default config +/// +/// ### EXPECTED +/// Contract is deployed with the default config +#[test] +fn test_deploy() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); +} + +/// ### TEST +/// S-FEE-MARKET_SET_FEE_OK +/// +/// ### ACTION +/// Call 'set_fee()' with valid fee struct +/// +/// ### EXPECTED +/// Fee is set successfully +#[test] +fn test_set_fee() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::from(PER_TRANSFER), + per_gas: BigUint::from(PER_GAS), + }, + }; + + state.set_fee(&fee, None); + + state + .common_setup + .world + .query() + .to(SOV_FEE_MARKET_ADDRESS) + .whitebox(sov_fee_market::contract_obj, |sc| { + assert!(!sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); +} + +/// ### TEST +/// S-FEE-MARKET_SET_FEE_FAIL +/// +/// ### ACTION +/// Call 'set_fee()' with invalid token ID +/// +/// ### EXPECTED +/// Error INVALID_TOKEN_ID +#[test] +fn test_set_fee_invalid_token() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(WRONG_TOKEN_ID), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(WRONG_TOKEN_ID), + per_transfer: BigUint::from(PER_TRANSFER), + per_gas: BigUint::from(PER_GAS), + }, + }; + + state.set_fee(&fee, Some(INVALID_TOKEN_ID)); + + state.check_token_fee_storage_is_empty(WRONG_TOKEN_ID); +} + +/// ### TEST +/// S-FEE-MARKET_SET_FEE_FAIL +/// +/// ### ACTION +/// Call 'set_fee()' with invalid fee type +/// +/// ### EXPECTED +/// Error INVALID_FEE_TYPE +#[test] +fn test_set_fee_invalid_fee_type() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::None, + }; + + state.set_fee(&fee, Some(INVALID_FEE_TYPE)); + + state.check_token_fee_storage_is_empty(FIRST_TEST_TOKEN); +} + +/// ### TEST +/// S-FEE-MARKET_SET_FEE_FAIL +/// +/// ### ACTION +/// Call 'set_fee()' with invalid fee amount +/// +/// ### EXPECTED +/// Error INVALID_FEE +#[test] +fn test_set_fee_invalid_fee() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type: FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(SECOND_TEST_TOKEN), + per_transfer: BigUint::zero(), + per_gas: BigUint::zero(), + }, + }; + + state.set_fee(&fee, Some(INVALID_FEE)); + + state.check_token_fee_storage_is_empty(FIRST_TEST_TOKEN); +} + +/// ### TEST +/// S-FEE-MARKET_REMOVE_FEE_OK +/// +/// ### ACTION +/// Call 'remove_fee()' with valid token ID +/// +/// ### EXPECTED +/// Fee is removed successfully +#[test] +fn test_remove_fee() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.remove_fee(FIRST_TEST_TOKEN, None); + + state.check_token_fee_storage_is_empty(FIRST_TEST_TOKEN); +} + +/// ### TEST +/// S-FEE-MARKET_REMOVE_FEE_FAIL +/// +/// ### ACTION +/// Call 'remove_fee()' with non-existent token ID +/// +/// ### EXPECTED +/// No error (should succeed even if token doesn't exist) +#[test] +fn test_remove_fee_non_existent() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + state.remove_fee(SECOND_TEST_TOKEN, None); + + state.check_token_fee_storage_is_empty(SECOND_TEST_TOKEN); +} + +/// ### TEST +/// S-FEE-MARKET_SUBTRACT_FEE_OK +/// +/// ### ACTION +/// Call 'subtract_fee()' with correct payment +/// +/// ### EXPECTED +/// Fee is subtracted successfully +#[test] +fn test_subtract_fee() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1, + OptionalValue::None, + None, + ); + + state.check_accumulated_fees(FIRST_TEST_TOKEN, PER_TRANSFER); +} + +/// ### TEST +/// S-FEE-MARKET_SUBTRACT_FEE_FAIL +/// +/// ### ACTION +/// Call 'subtract_fee()' with invalid token +/// +/// ### EXPECTED +/// Error TOKEN_NOT_ACCEPTED_AS_FEE +#[test] +fn test_subtract_fee_invalid_token() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::InvalidToken, + USER_ADDRESS.to_address(), + 1, + OptionalValue::None, + Some(TOKEN_NOT_ACCEPTED_AS_FEE), + ); + + state.check_accumulated_fees(SECOND_TEST_TOKEN, 0); +} + +/// ### TEST +/// S-FEE-MARKET_SUBTRACT_FEE_FAIL +/// +/// ### ACTION +/// Call 'subtract_fee()' with insufficient payment +/// +/// ### EXPECTED +/// Error PAYMENT_DOES_NOT_COVER_FEE +#[test] +fn test_subtract_fee_insufficient_payment() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::LessThanFee, + USER_ADDRESS.to_address(), + 1, + OptionalValue::None, + Some(PAYMENT_DOES_NOT_COVER_FEE), + ); + + state.check_accumulated_fees(FIRST_TEST_TOKEN, 0); +} + +/// ### TEST +/// S-FEE-MARKET_DISTRIBUTE_FEES_OK +/// +/// ### ACTION +/// Call 'distribute_fees()' with valid address percentage pairs +/// +/// ### EXPECTED +/// Fees are distributed successfully +#[test] +fn test_distribute_fees() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + state.subtract_fee( + WantedFeeType::Correct, + USER_ADDRESS.to_address(), + 1, + OptionalValue::None, + None, + ); + + state.check_accumulated_fees(FIRST_TEST_TOKEN, PER_TRANSFER); + + let pairs = vec![ + MultiValue2::from((USER_ADDRESS.to_managed_address(), 5000usize)), + MultiValue2::from((OWNER_ADDRESS.to_managed_address(), 5000usize)), + ]; + + state.distribute_fees(pairs, None); + + state.check_accumulated_fees(FIRST_TEST_TOKEN, 0); +} + +/// ### TEST +/// S-FEE-MARKET_DISTRIBUTE_FEES_FAIL +/// +/// ### ACTION +/// Call 'distribute_fees()' with invalid percentage sum +/// +/// ### EXPECTED +/// Error about invalid percentage sum +#[test] +fn test_distribute_fees_invalid_percentage() { + let mut state = SovFeeMarketTestState::new(); + + let fee = state.get_fee(); + state + .common_setup + .deploy_sov_fee_market(Some(fee), ESDT_SAFE_ADDRESS); + + let pairs = vec![ + MultiValue2::from((USER_ADDRESS.to_managed_address(), 6000usize)), + MultiValue2::from((OWNER_ADDRESS.to_managed_address(), 5000usize)), + ]; + + state.distribute_fees(pairs, Some(INVALID_PERCENTAGE_SUM)); + + state.check_accumulated_fees(FIRST_TEST_TOKEN, 0); +} + +/// ### TEST +/// S-FEE-MARKET_ADD_USERS_TO_WHITELIST_OK +/// +/// ### ACTION +/// Call 'add_users_to_whitelist()' with valid users +/// +/// ### EXPECTED +/// Users are added to whitelist successfully +#[test] +fn test_add_users_to_whitelist() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let users = vec![USER_ADDRESS.to_managed_address()]; + + state.add_users_to_whitelist(users, None); + + state + .common_setup + .world + .query() + .to(SOV_FEE_MARKET_ADDRESS) + .whitebox(sov_fee_market::contract_obj, |sc| { + let whitelist = sc.users_whitelist(); + assert!(!whitelist.is_empty()); + }); +} + +/// ### TEST +/// S-FEE-MARKET_REMOVE_USERS_FROM_WHITELIST_OK +/// +/// ### ACTION +/// Call 'remove_users_from_whitelist()' with valid users +/// +/// ### EXPECTED +/// Users are removed from whitelist successfully +#[test] +fn test_remove_users_from_whitelist() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let users = vec![USER_ADDRESS.to_managed_address()]; + state.add_users_to_whitelist(users, None); + + let users_to_remove = vec![USER_ADDRESS.to_managed_address()]; + state.remove_users_from_whitelist(users_to_remove, None); + + state + .common_setup + .world + .query() + .to(SOV_FEE_MARKET_ADDRESS) + .whitebox(sov_fee_market::contract_obj, |sc| { + let whitelist = sc.users_whitelist(); + assert!(whitelist.is_empty()); + }); +} + +/// ### TEST +/// S-FEE-MARKET_REMOVE_USERS_FROM_WHITELIST_FAIL +/// +/// ### ACTION +/// Call 'remove_users_from_whitelist()' with non-whitelisted users +/// +/// ### EXPECTED +/// Error ITEM_NOT_IN_LIST +#[test] +fn test_remove_users_from_whitelist_not_in_list() { + let mut state = SovFeeMarketTestState::new(); + + state + .common_setup + .deploy_sov_fee_market(None, ESDT_SAFE_ADDRESS); + + let users_to_remove = vec![USER_ADDRESS.to_managed_address()]; + state.remove_users_from_whitelist(users_to_remove, Some(ITEM_NOT_IN_LIST)); +} diff --git a/header-verifier/wasm-header-verifier-view/Cargo.lock b/sov-fee-market/wasm/Cargo.lock similarity index 70% rename from header-verifier/wasm-header-verifier-view/Cargo.lock rename to sov-fee-market/wasm/Cargo.lock index 26f3fd7b7..9ff8c58ab 100644 --- a/header-verifier/wasm-header-verifier-view/Cargo.lock +++ b/sov-fee-market/wasm/Cargo.lock @@ -10,64 +10,66 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] -name = "cross-chain" +name = "common-utils" version = "0.1.0" dependencies = [ + "custom-events", "error-messages", "multiversx-sc", - "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "header-verifier" +name = "fee-common" version = "0.1.0" dependencies = [ - "cross-chain", + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "proxies", "structs", ] [[package]] -name = "header-verifier-view-wasm" -version = "0.0.0" +name = "generic-array" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" dependencies = [ - "header-verifier", - "multiversx-sc-wasm-adapter", + "typenum", ] [[package]] @@ -78,15 +80,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -94,9 +96,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -110,9 +112,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -122,9 +124,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -134,9 +136,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -147,18 +149,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -183,9 +185,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -200,18 +202,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -219,9 +221,30 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sov-fee-market" +version = "0.1.0" +dependencies = [ + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "proxies", + "structs", +] + +[[package]] +name = "sov-fee-market-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "sov-fee-market", +] [[package]] name = "structs" @@ -232,9 +255,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -249,21 +272,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/fee-market/wasm-fee-market-view/Cargo.toml b/sov-fee-market/wasm/Cargo.toml similarity index 87% rename from fee-market/wasm-fee-market-view/Cargo.toml rename to sov-fee-market/wasm/Cargo.toml index bff8224c0..6be7a94ca 100644 --- a/fee-market/wasm-fee-market-view/Cargo.toml +++ b/sov-fee-market/wasm/Cargo.toml @@ -5,7 +5,7 @@ # ########################################## [package] -name = "fee-market-view-wasm" +name = "sov-fee-market-wasm" version = "0.0.0" edition = "2021" publish = false @@ -24,11 +24,11 @@ overflow-checks = false [profile.dev] panic = "abort" -[dependencies.fee-market] +[dependencies.sov-fee-market] path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/fee-market/wasm-fee-market/src/lib.rs b/sov-fee-market/wasm/src/lib.rs similarity index 85% rename from fee-market/wasm-fee-market/src/lib.rs rename to sov-fee-market/wasm/src/lib.rs index afd57dc7a..3aae653c3 100644 --- a/fee-market/wasm-fee-market/src/lib.rs +++ b/sov-fee-market/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 9 +// Endpoints: 8 // Async Callback (empty): 1 -// Total number of exported functions: 12 +// Total number of exported functions: 11 #![no_std] @@ -16,19 +16,18 @@ multiversx_sc_wasm_adapter::allocator!(); multiversx_sc_wasm_adapter::panic_handler!(); multiversx_sc_wasm_adapter::endpoints! { - fee_market + sov_fee_market ( init => init upgrade => upgrade - setPriceAggregatorAddress => set_price_aggregator_address - setFee => set_fee - removeFee => remove_fee - getTokenFee => token_fee addUsersToWhitelist => add_users_to_whitelist removeUsersFromWhitelist => remove_users_from_whitelist + getTokenFee => token_fee + getUsersWhitelist => users_whitelist distributeFees => distribute_fees + removeFee => remove_fee + setFee => set_fee subtractFee => subtract_fee - getUsersWhitelist => users_whitelist ) } diff --git a/sovereign-forge/Cargo.lock b/sovereign-forge/Cargo.lock new file mode 100644 index 000000000..d81c7ae88 --- /dev/null +++ b/sovereign-forge/Cargo.lock @@ -0,0 +1,1223 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "multiversx-chain-core" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c483122550dca9dd4286f970000d855e9d0184e4fe5c5527209a131ac1207638" +dependencies = [ + "bitflags", + "multiversx-sc-codec", +] + +[[package]] +name = "multiversx-chain-scenario-format" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcca77966bf5eb9c9f96d0597f17a4fa7b64681cc7b83e39bdf31f8c6ca04d44" +dependencies = [ + "bech32", + "hex", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "sha3", +] + +[[package]] +name = "multiversx-chain-vm" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb84ccafc930d8a9d84de56758cef7070bfce951475d3f751be447acd4404d7a" +dependencies = [ + "bitflags", + "colored", + "ed25519-dalek", + "hex", + "hex-literal", + "itertools", + "multiversx-chain-core", + "multiversx-chain-vm-executor", + "num-bigint", + "num-traits", + "rand", + "rand_seeder", + "sha2", + "sha3", +] + +[[package]] +name = "multiversx-chain-vm-executor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b59072fa0624b55ae5ae3fa6bfa91515bbeb4ac440214bc4a509e2c8806d6e9f" + +[[package]] +name = "multiversx-sc" +version = "0.54.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e8edabd1e3eea1c85f96485d88116a95f57429b8e4f002444d6465608d1bf5" +dependencies = [ + "bitflags", + "hex-literal", + "multiversx-chain-core", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b72f7f6a3e0828c5e38fe0e98298706837f8337fe06bca3ff21df22c84b6256" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "num-bigint", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b915f0f463fd1811de124b51d68cb2067f5a4796cde2a9570316780cf18961" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.54.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8712fed727b0f22094616c6398c821899e03f5998dab619acd54ece669de3e8" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-meta-lib" +version = "0.54.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ff342ab443d1815b1ec3c0be10048d3004fa62146307fc507f13787fe1503f" +dependencies = [ + "clap", + "colored", + "convert_case", + "hex", + "lazy_static", + "multiversx-sc", + "rustc_version", + "semver", + "serde", + "serde_json", + "toml", + "wasmparser 0.216.0", + "wasmprinter", + "wat", +] + +[[package]] +name = "multiversx-sc-scenario" +version = "0.54.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a4ad9cef428cd8f92012aedf43c68f677bb606efec199b0157b62d384fc17eb" +dependencies = [ + "base64", + "bech32", + "colored", + "hex", + "itertools", + "log", + "multiversx-chain-scenario-format", + "multiversx-chain-vm", + "multiversx-chain-vm-executor", + "multiversx-sc", + "multiversx-sc-meta-lib", + "num-bigint", + "num-traits", + "pathdiff", + "serde", + "serde_json", + "sha2", + "unwrap-infallible", +] + +[[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-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_seeder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9febe641d2842ffc76ee962668a17578767c4e01735e4802b21ed9a24b2e4e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "sovereign-forge" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-scenario", + "num-bigint", +] + +[[package]] +name = "sovereign-forge-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "sovereign-forge", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "unwrap-infallible" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-encoder" +version = "0.220.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf48234b389415b226a4daef6562933d38c7b28a8b8f64c5c4130dad1561ab7" +dependencies = [ + "leb128", + "wasmparser 0.220.0", +] + +[[package]] +name = "wasmparser" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" +dependencies = [ + "ahash", + "bitflags", + "hashbrown 0.14.5", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.220.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" +dependencies = [ + "bitflags", + "indexmap", +] + +[[package]] +name = "wasmprinter" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f82916f3892e53620639217d6ec78fe15c678352a3fbf3f3745b6417d0bd70f" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.216.0", +] + +[[package]] +name = "wast" +version = "220.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e708c8de08751fd66e70961a32bae9d71901f14a70871e181cb8461a3bb3165" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.220.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de4f1d7d59614ba690541360102b995c4eb1b9ed373701d5102cc1a968b1c5a3" +dependencies = [ + "wast", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/sovereign-forge/Cargo.toml b/sovereign-forge/Cargo.toml new file mode 100644 index 000000000..845e9be72 --- /dev/null +++ b/sovereign-forge/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "sovereign-forge" +version = "0.0.0" +publish = false +edition = "2021" +authors = ["you"] + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.63.0" + +[dependencies.multiversx-sc-modules] +version = "0.63.0" + +[dependencies.chain-factory] +path = "../chain-factory" + +[dependencies.chain-config] +path = "../chain-config" + +[dependencies.mvx-esdt-safe] +path = "../mvx-esdt-safe" + +[dependencies.cross-chain] +path = "../common/cross-chain" + +[dependencies.header-verifier] +path = "../header-verifier" + +[dependencies.mvx-fee-market] +path = "../mvx-fee-market" + +[dependencies.fee-common] +path = "../common/fee-common" + +[dependencies.proxies] +path = "../common/proxies" + +[dependencies.common-utils] +path = "../common/common-utils" + +[dependencies.custom-events] +path = "../common/custom-events" + +[dependencies.structs] +path = "../common/structs" + +[dependencies.error-messages] +path = "../common/error-messages" + +[dev-dependencies] +num-bigint = "0.4" + +[dev-dependencies.multiversx-sc-modules] +version = "0.63.0" + +[dev-dependencies.common-test-setup] +path = "../common/common-test-setup" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.63.0" diff --git a/sovereign-forge/README.md b/sovereign-forge/README.md new file mode 100644 index 000000000..dd567f434 --- /dev/null +++ b/sovereign-forge/README.md @@ -0,0 +1,13 @@ +# Sovereign Forge + +Orchestrates the four-phase deployment of a sovereign’s MultiversX-side contracts via Chain Factory and tracks chain IDs per creator. + +- **Initialization:** `init(opt_deploy_cost)` sets the required EGLD deposit for deployments and pauses the contract. +- **Registering factory/trusted tokens:** `registerChainFactory(shard_id, address)` wires a Chain Factory per shard. `registerTrustedToken` stores token IDs allowed for burn/lock mechanics downstream. +- **Deployment phases (caller = sovereign creator):** + - `deployPhaseOne(opt_preferred_chain_id, config)` → deploy Chain Config and reserve a chain ID. + - `deployPhaseTwo(opt_config)` → deploy MultiversX ESDT Safe with the reserved prefix. + - `deployPhaseThree(fee)` → deploy MultiversX Fee Market linked to the ESDT Safe. + - `deployPhaseFour()` → deploy Header Verifier with references to the other contracts. +- **Finishing setup:** `completeSetupPhase` triggers Chain Factory to run the per-contract `completeSetupPhase` calls and transfer ownership to the Header Verifier. Marks the sovereign as setup-complete. +- **Interactions:** Drives Chain Factory to clone templates. Validators and the Header Verifier rely on the chain ID generated here to namespace token IDs and contract lookups. diff --git a/sovereign-forge/meta/Cargo.toml b/sovereign-forge/meta/Cargo.toml new file mode 100644 index 000000000..d41e405b1 --- /dev/null +++ b/sovereign-forge/meta/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sovereign-forge-meta" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies.sovereign-forge] +path = ".." + +[dependencies.multiversx-sc-meta-lib] +default-features = false +version = "0.63.0" diff --git a/sovereign-forge/meta/src/main.rs b/sovereign-forge/meta/src/main.rs new file mode 100644 index 000000000..7ea22ec04 --- /dev/null +++ b/sovereign-forge/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta_lib::cli_main::(); +} diff --git a/sovereign-forge/multiversx.json b/sovereign-forge/multiversx.json new file mode 100644 index 000000000..736553962 --- /dev/null +++ b/sovereign-forge/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/sovereign-forge/sc-config.toml b/sovereign-forge/sc-config.toml new file mode 100644 index 000000000..b0605f53f --- /dev/null +++ b/sovereign-forge/sc-config.toml @@ -0,0 +1,2 @@ +[[proxy]] +path = "../common/proxies/src/sovereign_forge_proxy.rs" diff --git a/sovereign-forge/scenarios/sovereign_forge.scen.json b/sovereign-forge/scenarios/sovereign_forge.scen.json new file mode 100644 index 000000000..c3c748a4c --- /dev/null +++ b/sovereign-forge/scenarios/sovereign_forge.scen.json @@ -0,0 +1,39 @@ +{ + "name": "empty", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "1", + "balance": "0" + } + }, + "newAddresses": [ + { + "creatorAddress": "address:owner", + "creatorNonce": "1", + "newAddress": "sc:empty" + } + ] + }, + { + "step": "scDeploy", + "id": "deploy", + "tx": { + "from": "address:owner", + "contractCode": "mxsc:../output/sovereign-forge.mxsc.json", + "arguments": [], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": [], + "gas": "*", + "refund": "*" + } + } + ] +} diff --git a/sovereign-forge/src/forge_common/callbacks.rs b/sovereign-forge/src/forge_common/callbacks.rs new file mode 100644 index 000000000..909c6a74d --- /dev/null +++ b/sovereign-forge/src/forge_common/callbacks.rs @@ -0,0 +1,58 @@ +use crate::err_msg; +use multiversx_sc::{imports::IgnoreValue, sc_panic, types::ManagedAsyncCallResult}; +use structs::forge::{ContractInfo, ScArray}; + +use crate::forge_common::{forge_utils, storage}; + +#[multiversx_sc::module] +pub trait ForgeCallbackModule: + forge_utils::ForgeUtilsModule + + storage::StorageModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule +{ + #[promises_callback] + fn setup_phase( + &self, + sovereign_owner: &ManagedAddress, + #[call_result] result: ManagedAsyncCallResult, + ) { + match result { + ManagedAsyncCallResult::Ok(_) => { + self.sovereign_setup_phase(&self.sovereigns_mapper(sovereign_owner).get()) + .set(true); + } + ManagedAsyncCallResult::Err(result) => { + sc_panic!(result.err_msg); + } + } + } + + #[promises_callback] + fn register_deployed_contract( + &self, + chain_id: &ManagedBuffer, + sc_id: ScArray, + #[call_result] result: ManagedAsyncCallResult, + ) { + match result { + ManagedAsyncCallResult::Ok(sc_address) => { + let new_contract_info = ContractInfo::new(sc_id, sc_address); + + self.sovereign_deployed_contracts(chain_id) + .insert(new_contract_info); + } + ManagedAsyncCallResult::Err(call_err) => { + sc_panic!(call_err.err_msg); + } + } + } + + #[promises_callback] + fn update_configs(&self, #[call_result] result: ManagedAsyncCallResult) { + match result { + ManagedAsyncCallResult::Ok(_) => {} + ManagedAsyncCallResult::Err(err) => sc_panic!("{}", err.err_msg), + } + } +} diff --git a/sovereign-forge/src/forge_common/forge_utils.rs b/sovereign-forge/src/forge_common/forge_utils.rs new file mode 100644 index 000000000..01bcee0f5 --- /dev/null +++ b/sovereign-forge/src/forge_common/forge_utils.rs @@ -0,0 +1,116 @@ +use error_messages::{ + CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN, CHAIN_CONFIG_NOT_DEPLOYED, CHAIN_ID_ALREADY_IN_USE, + ESDT_SAFE_NOT_DEPLOYED, FEE_MARKET_NOT_DEPLOYED, HEADER_VERIFIER_NOT_DEPLOYED, +}; +use multiversx_sc::err_msg; +use multiversx_sc::require; +use structs::forge::ScArray; + +const CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz"; + +pub const NUMBER_OF_SHARDS: u32 = 3; + +#[multiversx_sc::module] +pub trait ForgeUtilsModule: + super::storage::StorageModule + common_utils::CommonUtilsModule + custom_events::CustomEventsModule +{ + fn require_initialization_phase_complete(&self, shard_id: u32) { + require!( + !self.chain_factories(shard_id).is_empty(), + "There is no Chain-Factory contract assigned for shard {}", + shard_id + ); + } + + fn require_phase_four_completed(&self, caller: &ManagedAddress) { + require!( + self.is_contract_deployed(caller, ScArray::HeaderVerifier), + HEADER_VERIFIER_NOT_DEPLOYED + ); + } + + fn require_phase_three_completed(&self, caller: &ManagedAddress) { + require!( + self.is_contract_deployed(caller, ScArray::FeeMarket), + FEE_MARKET_NOT_DEPLOYED + ); + } + + fn require_phase_two_completed(&self, caller: &ManagedAddress) { + require!( + self.is_contract_deployed(caller, ScArray::ESDTSafe), + ESDT_SAFE_NOT_DEPLOYED + ); + } + + fn require_phase_one_completed(&self, caller: &ManagedAddress) { + require!( + !self.sovereigns_mapper(caller).is_empty(), + CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN + ); + + require!( + self.is_contract_deployed(caller, ScArray::ChainConfig), + CHAIN_CONFIG_NOT_DEPLOYED + ); + } + + fn is_contract_deployed(&self, sovereign_creator: &ManagedAddress, sc_id: ScArray) -> bool { + let chain_id = self.sovereigns_mapper(sovereign_creator).get(); + self.sovereign_deployed_contracts(&chain_id) + .iter() + .any(|sc| sc.id == sc_id) + } + + fn get_contract_address(&self, caller: &ManagedAddress, sc_id: ScArray) -> ManagedAddress { + let chain_id = self.sovereigns_mapper(caller).get(); + + self.sovereign_deployed_contracts(&chain_id) + .iter() + .find(|sc| sc.id == sc_id) + .unwrap() + .address + } + + fn generate_chain_id(&self, opt_preferred_chain_id: Option) -> ManagedBuffer { + let mut chain_id_history_mapper = self.chain_ids(); + + match opt_preferred_chain_id { + Some(preferred_chain_id) => { + self.validate_chain_id(&preferred_chain_id); + + require!( + !chain_id_history_mapper.contains(&preferred_chain_id), + CHAIN_ID_ALREADY_IN_USE + ); + + chain_id_history_mapper.insert(preferred_chain_id.clone()); + + preferred_chain_id + } + None => loop { + let new_chain_id = self.generated_random_four_char_string(); + if !chain_id_history_mapper.contains(&new_chain_id) { + chain_id_history_mapper.insert(new_chain_id.clone()); + break new_chain_id; + } + }, + } + } + + fn generated_random_four_char_string(&self) -> ManagedBuffer { + let mut byte_array: [u8; 4] = [0; 4]; + let mut rand = RandomnessSource::new(); + (0..4).for_each(|i| { + let rand_index = rand.next_u8_in_range(0, CHARSET.len() as u8) as usize; + byte_array[i] = CHARSET[rand_index]; + }); + ManagedBuffer::new_from_bytes(&byte_array) + } + + fn get_chain_factory_address(&self, caller: &ManagedAddress) -> ManagedAddress { + let shard_id = self.blockchain().get_shard_of_address(caller); + + self.chain_factories(shard_id).get() + } +} diff --git a/sovereign-forge/src/forge_common/mod.rs b/sovereign-forge/src/forge_common/mod.rs new file mode 100644 index 000000000..cafcf336f --- /dev/null +++ b/sovereign-forge/src/forge_common/mod.rs @@ -0,0 +1,4 @@ +pub mod callbacks; +pub mod forge_utils; +pub mod sc_deploy; +pub mod storage; diff --git a/sovereign-forge/src/forge_common/sc_deploy.rs b/sovereign-forge/src/forge_common/sc_deploy.rs new file mode 100644 index 000000000..6a83ad34c --- /dev/null +++ b/sovereign-forge/src/forge_common/sc_deploy.rs @@ -0,0 +1,108 @@ +use crate::{ + err_msg, + forge_common::callbacks::{self, CallbackProxy}, +}; +use multiversx_sc::{imports::OptionalValue, types::MultiValueEncoded}; +use proxies::chain_factory_proxy::ChainFactoryContractProxy; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, + forge::{ContractInfo, ScArray}, + PHASE_FOUR_ASYNC_CALL_GAS, PHASE_FOUR_CALLBACK_GAS, PHASE_ONE_ASYNC_CALL_GAS, + PHASE_ONE_CALLBACK_GAS, PHASE_THREE_ASYNC_CALL_GAS, PHASE_THREE_CALLBACK_GAS, + PHASE_TWO_ASYNC_CALL_GAS, PHASE_TWO_CALLBACK_GAS, +}; + +#[multiversx_sc::module] +pub trait ScDeployModule: + super::forge_utils::ForgeUtilsModule + + super::storage::StorageModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + callbacks::ForgeCallbackModule +{ + #[inline] + fn deploy_chain_config( + &self, + sovereign_owner: &ManagedAddress, + chain_id: &ManagedBuffer, + config: OptionalValue>, + ) { + self.tx() + .to(self.get_chain_factory_address(sovereign_owner)) + .typed(ChainFactoryContractProxy) + .deploy_sovereign_chain_config_contract(config) + .gas(PHASE_ONE_ASYNC_CALL_GAS) + .callback( + self.callbacks() + .register_deployed_contract(chain_id, ScArray::ChainConfig), + ) + .gas_for_callback(PHASE_ONE_CALLBACK_GAS) + .register_promise(); + } + + #[inline] + fn deploy_mvx_esdt_safe( + &self, + sovereign_owner: ManagedAddress, + sov_prefix: ManagedBuffer, + opt_config: OptionalValue>, + ) { + let chain_id = self.sovereigns_mapper(&sovereign_owner).get(); + + self.tx() + .to(self.get_chain_factory_address(&sovereign_owner)) + .typed(ChainFactoryContractProxy) + .deploy_mvx_esdt_safe(sovereign_owner, sov_prefix, opt_config) + .gas(PHASE_TWO_ASYNC_CALL_GAS) + .callback( + self.callbacks() + .register_deployed_contract(&chain_id, ScArray::ESDTSafe), + ) + .gas_for_callback(PHASE_TWO_CALLBACK_GAS) + .register_promise(); + } + + #[inline] + fn deploy_fee_market( + &self, + sovereign_owner: &ManagedAddress, + esdt_safe_address: &ManagedAddress, + fee: OptionalValue>, + ) { + let chain_id = self.sovereigns_mapper(sovereign_owner).get(); + + self.tx() + .to(self.get_chain_factory_address(sovereign_owner)) + .typed(ChainFactoryContractProxy) + .deploy_fee_market(esdt_safe_address, fee.into_option()) + .gas(PHASE_THREE_ASYNC_CALL_GAS) + .callback( + self.callbacks() + .register_deployed_contract(&chain_id, ScArray::FeeMarket), + ) + .gas_for_callback(PHASE_THREE_CALLBACK_GAS) + .register_promise(); + } + + #[inline] + fn deploy_header_verifier( + &self, + sovereign_owner: &ManagedAddress, + sovereign_contract: MultiValueEncoded>, + ) { + let chain_id = self.sovereigns_mapper(sovereign_owner).get(); + + self.tx() + .to(self.get_chain_factory_address(sovereign_owner)) + .typed(ChainFactoryContractProxy) + .deploy_header_verifier(sovereign_contract) + .gas(PHASE_FOUR_ASYNC_CALL_GAS) + .callback( + self.callbacks() + .register_deployed_contract(&chain_id, ScArray::HeaderVerifier), + ) + .gas_for_callback(PHASE_FOUR_CALLBACK_GAS) + .register_promise(); + } +} diff --git a/sovereign-forge/src/forge_common/storage.rs b/sovereign-forge/src/forge_common/storage.rs new file mode 100644 index 000000000..e4b5cf2e5 --- /dev/null +++ b/sovereign-forge/src/forge_common/storage.rs @@ -0,0 +1,43 @@ +use multiversx_sc::{ + imports::{SingleValueMapper, UnorderedSetMapper}, + types::ManagedBuffer, +}; +use structs::forge::ContractInfo; + +pub type ChainId = ManagedBuffer; + +#[multiversx_sc::module] +pub trait StorageModule { + #[storage_mapper("sovereignsMapper")] + fn sovereigns_mapper( + &self, + sovereign_creator: &ManagedAddress, + ) -> SingleValueMapper>; + + #[view(getDeployedSovereignContracts)] + #[storage_mapper("sovereignDeployedContracts")] + fn sovereign_deployed_contracts( + &self, + chain_id: &ChainId, + ) -> UnorderedSetMapper>; + + #[view(getTrustedTokens)] + #[storage_mapper("trustedTokens")] + fn trusted_tokens(&self) -> UnorderedSetMapper; + + #[view(getSovereignSetupPhase)] + #[storage_mapper("sovereignSetupPhase")] + fn sovereign_setup_phase(&self, chain_id: &ChainId) -> SingleValueMapper; + + #[view(getChainFactoryAddress)] + #[storage_mapper("chainFactories")] + fn chain_factories(&self, shard_id: u32) -> SingleValueMapper; + + #[view(getDeployCost)] + #[storage_mapper("deployCost")] + fn deploy_cost(&self) -> SingleValueMapper; + + #[view(getAllChainIds)] + #[storage_mapper("allChainIds")] + fn chain_ids(&self) -> UnorderedSetMapper; +} diff --git a/sovereign-forge/src/lib.rs b/sovereign-forge/src/lib.rs new file mode 100644 index 000000000..4caad908b --- /dev/null +++ b/sovereign-forge/src/lib.rs @@ -0,0 +1,63 @@ +#![no_std] + +use crate::err_msg; +use error_messages::{ADDRESS_NOT_VALID_SC_ADDRESS, CHAIN_FACTORY_ADDRESS_NOT_IN_EXPECTED_SHARD}; +use multiversx_sc::imports::*; +use multiversx_sc_modules::pause; + +pub mod forge_common; +pub mod phases; +pub mod update_configs; + +#[multiversx_sc::contract] +pub trait SovereignForge: + phases::PhasesModule + + forge_common::storage::StorageModule + + forge_common::forge_utils::ForgeUtilsModule + + forge_common::sc_deploy::ScDeployModule + + forge_common::callbacks::ForgeCallbackModule + + update_configs::UpdateConfigsModule + + common_utils::CommonUtilsModule + + custom_events::CustomEventsModule + + pause::PauseModule +{ + #[init] + fn init(&self, opt_deploy_cost: OptionalValue) { + match opt_deploy_cost { + OptionalValue::Some(deploy_cost) => self.deploy_cost().set(deploy_cost), + OptionalValue::None => self.deploy_cost().set(BigUint::zero()), + } + self.pause_endpoint(); + } + + #[only_owner] + #[endpoint(registerChainFactory)] + fn register_chain_factory(&self, shard_id: u32, chain_factory_address: ManagedAddress) { + require!( + shard_id < forge_common::forge_utils::NUMBER_OF_SHARDS, + "Shard id {} is out of range", + shard_id + ); + require!( + self.blockchain() + .get_shard_of_address(&chain_factory_address) + == shard_id, + CHAIN_FACTORY_ADDRESS_NOT_IN_EXPECTED_SHARD, + ); + require!( + self.blockchain().is_smart_contract(&chain_factory_address), + ADDRESS_NOT_VALID_SC_ADDRESS + ); + + self.chain_factories(shard_id).set(chain_factory_address); + } + + #[only_owner] + #[endpoint(registerTrustedToken)] + fn register_trusted_token(&self, trusted_token: ManagedBuffer) { + self.trusted_tokens().insert(trusted_token); + } + + #[upgrade] + fn upgrade(&self) {} +} diff --git a/sovereign-forge/src/phases.rs b/sovereign-forge/src/phases.rs new file mode 100644 index 000000000..5b2501551 --- /dev/null +++ b/sovereign-forge/src/phases.rs @@ -0,0 +1,156 @@ +use crate::{ + err_msg, + forge_common::{ + self, + callbacks::{self, CallbackProxy}, + }, +}; + +use error_messages::{ + CHAIN_CONFIG_ALREADY_DEPLOYED, DEPLOY_COST_NOT_ENOUGH, ESDT_SAFE_ALREADY_DEPLOYED, + FEE_MARKET_ALREADY_DEPLOYED, HEADER_VERIFIER_ALREADY_DEPLOYED, + SOVEREIGN_SETUP_PHASE_ALREADY_COMPLETED, +}; +use multiversx_sc::{imports::OptionalValue, require, types::MultiValueEncoded}; +use multiversx_sc_modules::pause; +use proxies::chain_factory_proxy::ChainFactoryContractProxy; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, + forge::ScArray, + COMPLETE_SETUP_PHASE_CALLBACK_GAS, COMPLETE_SETUP_PHASE_GAS, +}; + +#[multiversx_sc::module] +pub trait PhasesModule: + forge_common::forge_utils::ForgeUtilsModule + + forge_common::storage::StorageModule + + forge_common::sc_deploy::ScDeployModule + + custom_events::CustomEventsModule + + common_utils::CommonUtilsModule + + callbacks::ForgeCallbackModule + + pause::PauseModule +{ + #[payable("EGLD")] + #[endpoint(deployPhaseOne)] + fn deploy_phase_one( + &self, + opt_preferred_chain_id: Option, + config: OptionalValue>, + ) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + let caller_shard_id = blockchain_api.get_shard_of_address(&caller); + + self.require_initialization_phase_complete(caller_shard_id); + + let call_value = self.call_value().egld().clone(); + require!( + call_value == self.deploy_cost().get(), + DEPLOY_COST_NOT_ENOUGH + ); + + let chain_id = self.generate_chain_id(opt_preferred_chain_id); + + let chain_factories_mapper = self.chain_factories(caller_shard_id); + require!( + !chain_factories_mapper.is_empty(), + "There is no Chain-Factory address registered in shard {}", + caller_shard_id + ); + + require!( + !self.is_contract_deployed(&caller, ScArray::ChainConfig), + CHAIN_CONFIG_ALREADY_DEPLOYED + ); + + self.deploy_chain_config(&caller, &chain_id, config); + self.sovereigns_mapper(&caller).set(chain_id); + } + + #[endpoint(deployPhaseTwo)] + fn deploy_phase_two(&self, opt_config: OptionalValue>) { + self.require_not_paused(); + let caller = self.blockchain().get_caller(); + let sov_prefix = self.sovereigns_mapper(&caller).get(); + + self.require_phase_one_completed(&caller); + require!( + !self.is_contract_deployed(&caller, ScArray::ESDTSafe), + ESDT_SAFE_ALREADY_DEPLOYED + ); + + self.deploy_mvx_esdt_safe(caller, sov_prefix, opt_config); + } + + #[endpoint(deployPhaseThree)] + fn deploy_phase_three(&self, fee: OptionalValue>) { + self.require_not_paused(); + let caller = self.blockchain().get_caller(); + + self.require_phase_two_completed(&caller); + require!( + !self.is_contract_deployed(&caller, ScArray::FeeMarket), + FEE_MARKET_ALREADY_DEPLOYED + ); + + let esdt_safe_address = self.get_contract_address(&caller, ScArray::ESDTSafe); + + self.deploy_fee_market(&caller, &esdt_safe_address, fee); + } + + #[endpoint(deployPhaseFour)] + fn deploy_phase_four(&self) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_three_completed(&caller); + require!( + !self.is_contract_deployed(&caller, ScArray::HeaderVerifier), + HEADER_VERIFIER_ALREADY_DEPLOYED + ); + + let contract_addresses = MultiValueEncoded::from_iter( + self.sovereign_deployed_contracts(&self.sovereigns_mapper(&caller).get()) + .iter(), + ); + + self.deploy_header_verifier(&caller, contract_addresses); + } + + #[endpoint(completeSetupPhase)] + fn complete_setup_phase(&self) { + self.require_not_paused(); + let caller = self.blockchain().get_caller(); + let sovereign_setup_phase_mapper = + self.sovereign_setup_phase(&self.sovereigns_mapper(&caller).get()); + + require!( + sovereign_setup_phase_mapper.is_empty(), + SOVEREIGN_SETUP_PHASE_ALREADY_COMPLETED + ); + + self.require_phase_four_completed(&caller); + + let chain_config_address = self.get_contract_address(&caller, ScArray::ChainConfig); + let header_verifier_address = self.get_contract_address(&caller, ScArray::HeaderVerifier); + let esdt_safe_address = self.get_contract_address(&caller, ScArray::ESDTSafe); + let fee_market_address = self.get_contract_address(&caller, ScArray::FeeMarket); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .complete_setup_phase( + chain_config_address, + header_verifier_address, + esdt_safe_address, + fee_market_address, + ) + .gas(COMPLETE_SETUP_PHASE_GAS) + .callback(self.callbacks().setup_phase(&caller)) + .gas_for_callback(COMPLETE_SETUP_PHASE_CALLBACK_GAS) + .register_promise(); + } +} diff --git a/sovereign-forge/src/update_configs.rs b/sovereign-forge/src/update_configs.rs new file mode 100644 index 000000000..915222712 --- /dev/null +++ b/sovereign-forge/src/update_configs.rs @@ -0,0 +1,195 @@ +use multiversx_sc::types::{EsdtTokenIdentifier, MultiValueEncoded}; +use multiversx_sc_modules::pause; +use proxies::chain_factory_proxy::ChainFactoryContractProxy; +use structs::configs::{EsdtSafeConfig, SovereignConfig}; +use structs::fee::FeeStruct; +use structs::forge::ScArray; +use structs::{UPDATE_CONFIGS_CALLBACK_GAS, UPDATE_CONFIGS_GAS}; + +use crate::err_msg; +use crate::forge_common; +use crate::forge_common::callbacks::{self, CallbackProxy}; + +#[multiversx_sc::module] +pub trait UpdateConfigsModule: + common_utils::CommonUtilsModule + + forge_common::storage::StorageModule + + forge_common::forge_utils::ForgeUtilsModule + + custom_events::CustomEventsModule + + callbacks::ForgeCallbackModule + + pause::PauseModule +{ + #[endpoint(updateEsdtSafeConfig)] + fn update_esdt_safe_config(&self, new_config: EsdtSafeConfig) { + self.require_not_paused(); + let caller = self.blockchain().get_caller(); + + self.require_phase_two_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .update_esdt_safe_config( + self.get_contract_address(&caller, ScArray::ESDTSafe), + new_config, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(updateSovereignConfig)] + fn update_sovereign_config(&self, new_config: SovereignConfig) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_one_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .update_sovereign_config( + self.get_contract_address(&caller, ScArray::ChainConfig), + new_config, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(setFee)] + fn set_fee(&self, new_fee: FeeStruct) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_three_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .set_fee( + self.get_contract_address(&caller, ScArray::FeeMarket), + new_fee, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(removeFee)] + fn remove_fee(&self, token_id: EsdtTokenIdentifier) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_three_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .remove_fee( + self.get_contract_address(&caller, ScArray::FeeMarket), + token_id, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(addUsersToWhitelist)] + fn add_users_to_whitelist(&self, users: MultiValueEncoded) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_three_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .add_users_to_whitelist( + self.get_contract_address(&caller, ScArray::FeeMarket), + users, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(removeUsersFromWhitelist)] + fn remove_users_from_whitelist(&self, users: MultiValueEncoded) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_three_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .remove_users_from_whitelist( + self.get_contract_address(&caller, ScArray::FeeMarket), + users, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(setTokenBurnMechanism)] + fn set_token_burn_mechanism(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_two_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .set_token_burn_mechanism( + self.get_contract_address(&caller, ScArray::ESDTSafe), + token_id, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[endpoint(setTokenLockMechanism)] + fn set_token_lock_mechanism(&self, token_id: EgldOrEsdtTokenIdentifier) { + self.require_not_paused(); + let blockchain_api = self.blockchain(); + let caller = blockchain_api.get_caller(); + + self.require_phase_two_completed(&caller); + + self.tx() + .to(self.get_chain_factory_address(&caller)) + .typed(ChainFactoryContractProxy) + .set_token_lock_mechanism( + self.get_contract_address(&caller, ScArray::ESDTSafe), + token_id, + ) + .gas(UPDATE_CONFIGS_GAS) + .callback(self.callbacks().update_configs()) + .gas_for_callback(UPDATE_CONFIGS_CALLBACK_GAS) + .register_promise(); + } + + #[only_owner] + #[endpoint(updateDeployCost)] + fn update_deploy_cost(&self, deploy_cost: BigUint) { + self.require_not_paused(); + self.deploy_cost().set(deploy_cost); + } +} diff --git a/sovereign-forge/tests/sovereign_forge_blackbox_setup.rs b/sovereign-forge/tests/sovereign_forge_blackbox_setup.rs new file mode 100644 index 000000000..5e3a4c426 --- /dev/null +++ b/sovereign-forge/tests/sovereign_forge_blackbox_setup.rs @@ -0,0 +1,315 @@ +use common_test_setup::{ + base_setup::init::{AccountSetup, BaseSetup}, + constants::{ + CHAIN_FACTORY_SC_ADDRESS, ESDT_SAFE_ADDRESS, OWNER_ADDRESS, OWNER_BALANCE, + SOVEREIGN_FORGE_SC_ADDRESS, + }, +}; +use multiversx_sc::{ + imports::{Bech32Address, OptionalValue}, + types::{ + BigUint, ManagedAddress, ManagedVec, MultiValueEncoded, ReturnsHandledOrError, + ReturnsResult, ReturnsResultUnmanaged, TestSCAddress, TestTokenIdentifier, + }, +}; +use multiversx_sc_scenario::{api::StaticApi, ScenarioTxRun, ScenarioTxWhitebox}; +use proxies::sovereign_forge_proxy::SovereignForgeProxy; +use sovereign_forge::forge_common::storage::{ChainId, StorageModule}; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::FeeStruct, + forge::ScArray, +}; + +pub struct SovereignForgeTestState { + pub common_setup: BaseSetup, +} + +impl SovereignForgeTestState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let owner_setup = AccountSetup { + address: OWNER_ADDRESS.to_address(), + code_path: None, + esdt_balances: None, + egld_balance: Some(BigUint::from(OWNER_BALANCE)), + }; + + let account_setups = vec![owner_setup]; + + let common_setup = BaseSetup::new(account_setups); + + Self { common_setup } + } + + pub fn finish_setup(&mut self) { + self.common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + sc.chain_factories(0) + .set(CHAIN_FACTORY_SC_ADDRESS.to_managed_address()); + sc.chain_factories(1) + .set(CHAIN_FACTORY_SC_ADDRESS.to_managed_address()); + sc.chain_factories(2) + .set(CHAIN_FACTORY_SC_ADDRESS.to_managed_address()); + + assert!(!sc.chain_factories(0).is_empty()); + assert!(!sc.chain_factories(1).is_empty()); + assert!(!sc.chain_factories(2).is_empty()); + }); + } + + pub fn deploy_template_scs(&mut self, templates: Option>) { + for sc in templates.unwrap_or_default().into_iter() { + match sc { + ScArray::ChainConfig => { + self.common_setup + .deploy_chain_config(OptionalValue::None, None); + } + ScArray::ESDTSafe => { + self.common_setup.deploy_mvx_esdt_safe(OptionalValue::None); + } + ScArray::FeeMarket => { + self.common_setup.deploy_fee_market(None, ESDT_SAFE_ADDRESS); + } + ScArray::HeaderVerifier => { + self.common_setup.deploy_header_verifier(vec![]); + } + ScArray::ChainFactory => { + self.common_setup.deploy_chain_factory(); + } + } + } + } + + pub fn register_chain_factory(&mut self, shard_id: u32, chain_factory_address: TestSCAddress) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .register_chain_factory(shard_id, chain_factory_address) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + } + + pub fn update_sovereign_config( + &mut self, + new_sovereign_config: SovereignConfig, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .update_sovereign_config(new_sovereign_config) + .returns(ReturnsHandledOrError::new()) + .run(); + + if let Err(error) = response { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + + pub fn update_esdt_safe_config( + &mut self, + new_esdt_safe_config: EsdtSafeConfig, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .update_esdt_safe_config(new_esdt_safe_config) + .returns(ReturnsHandledOrError::new()) + .run(); + + if let Err(error) = response { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + + pub fn set_fee(&mut self, new_fee: FeeStruct, expected_error_message: Option<&str>) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .set_fee(new_fee) + .returns(ReturnsHandledOrError::new()) + .run(); + + if let Err(error) = response { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + + pub fn remove_fee( + &mut self, + token_id: TestTokenIdentifier, + expected_error_message: Option<&str>, + ) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .remove_fee(token_id) + .returns(ReturnsHandledOrError::new()) + .run(); + + if let Err(error) = response { + assert_eq!(expected_error_message, Some(error.message.as_str())) + } + } + + pub fn complete_setup_phase(&mut self, expected_error_message: Option<&str>) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .complete_setup_phase() + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, expected_error_message); + } + + pub fn add_users_to_whitelist(&mut self, users: Vec>) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .add_users_to_whitelist(MultiValueEncoded::from(ManagedVec::from_iter(users))) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + } + + pub fn remove_users_from_whitelist(&mut self, users: Vec>) { + let response = self + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .remove_users_from_whitelist(MultiValueEncoded::from(ManagedVec::from_iter(users))) + .returns(ReturnsHandledOrError::new()) + .run(); + + self.common_setup + .assert_expected_error_message(response, None); + } + + pub fn get_smart_contract_address_from_sovereign_forge( + &mut self, + chain_id: ChainId, + sc_id: ScArray, + ) -> ManagedAddress { + self.common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(chain_id) + .returns(ReturnsResultUnmanaged) + .run() + .iter() + .find(|sc| sc.id == sc_id) + .unwrap() + .address + .clone() + } + + pub fn _check_setup_phase_completed( + &mut self, + chain_id: ChainId, + expected_result: bool, + ) { + let response = self + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .sovereign_setup_phase(chain_id) + .returns(ReturnsResultUnmanaged) + .run(); + + assert_eq!(response, expected_result); + } + + pub fn retrieve_deployed_mvx_esdt_safe_address( + &mut self, + preferred_chain_id: ChainId, + ) -> Bech32Address { + let sc_addresses = self + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(preferred_chain_id) + .returns(ReturnsResult) + .run(); + + for contract in sc_addresses { + let address = Bech32Address::from(contract.address.to_address()); + if contract.id == ScArray::ESDTSafe { + return address; + } + } + Bech32Address::zero_default_hrp() + } + + pub fn retrieve_deployed_chain_config_address( + &mut self, + preferred_chain_id: ChainId, + ) -> Bech32Address { + let sc_addresses = self + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .typed(SovereignForgeProxy) + .sovereign_deployed_contracts(preferred_chain_id) + .returns(ReturnsResult) + .run(); + + for contract in sc_addresses { + let address = Bech32Address::from(contract.address.to_address()); + if contract.id == ScArray::ChainConfig { + return address; + } + } + Bech32Address::zero_default_hrp() + } +} diff --git a/sovereign-forge/tests/sovereign_forge_blackbox_tests.rs b/sovereign-forge/tests/sovereign_forge_blackbox_tests.rs new file mode 100644 index 000000000..bdca91aad --- /dev/null +++ b/sovereign-forge/tests/sovereign_forge_blackbox_tests.rs @@ -0,0 +1,1353 @@ +use chain_config::storage::ChainConfigStorageModule; +use common_test_setup::{ + base_setup::helpers::BLSKey, + constants::{ + CHAIN_FACTORY_SC_ADDRESS, CHAIN_ID, DEPLOY_COST, ESDT_SAFE_ADDRESS, FIRST_TEST_TOKEN, + NATIVE_TEST_TOKEN, ONE_HUNDRED_THOUSAND, OWNER_ADDRESS, SOVEREIGN_FORGE_SC_ADDRESS, + USER_ADDRESS, + }, +}; +use cross_chain::storage::CrossChainStorage; +use error_messages::{ + CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN, CHAIN_CONFIG_ALREADY_DEPLOYED, CHAIN_ID_ALREADY_IN_USE, + CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC, DEPLOY_COST_NOT_ENOUGH, ESDT_SAFE_ALREADY_DEPLOYED, + ESDT_SAFE_NOT_DEPLOYED, FEE_MARKET_ALREADY_DEPLOYED, FEE_MARKET_NOT_DEPLOYED, + HEADER_VERIFIER_ALREADY_DEPLOYED, HEADER_VERIFIER_NOT_DEPLOYED, INVALID_CHAIN_ID, +}; +use fee_common::storage::FeeCommonStorageModule; +use multiversx_sc::{ + imports::OptionalValue, + types::{ + BigUint, EgldOrEsdtTokenIdentifier, ManagedBuffer, MultiEgldOrEsdtPayment, + ReturnsResultUnmanaged, + }, +}; +use multiversx_sc_scenario::{ScenarioTxRun, ScenarioTxWhitebox}; +use proxies::chain_config_proxy::ChainConfigContractProxy; +use sovereign_forge::forge_common::{forge_utils::ForgeUtilsModule, storage::StorageModule}; +use sovereign_forge_blackbox_setup::SovereignForgeTestState; +use structs::{ + configs::{EsdtSafeConfig, SovereignConfig}, + fee::{FeeStruct, FeeType}, + forge::ScArray, +}; +mod sovereign_forge_blackbox_setup; + +/// ### TEST +/// S-FORGE_DEPLOY_OK +/// +/// ### ACTION +/// Deploy sovereign_forge and chain_factory +/// +/// ### EXPECTED +/// Both sovereign_forge and chain_factory contracts deploy successfully +#[test] +fn test_deploy_contracts() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + state.common_setup.deploy_chain_factory(); +} + +/// ### TEST +/// S-FORGE_REGISTER_CHAIN_FACTORY_OK +/// +/// ### ACTION +/// Register chain_factory any shard +/// +/// ### EXPECTED +/// chain_factories() storage non-empty +#[test] +fn test_register_chain_factory() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.register_chain_factory(2, CHAIN_FACTORY_SC_ADDRESS); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc.chain_factories(2).is_empty()); + }); +} + +/// ### TEST +/// S-FORGE_UPDATE_CONFIG_FAIL +/// +/// ### ACTION +/// Update config without deploying chain_config +/// +/// ### EXPECTED +/// Error CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN +#[test] +fn test_update_sovereign_config_no_chain_config_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.register_chain_factory(2, CHAIN_FACTORY_SC_ADDRESS); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc.chain_factories(2).is_empty()); + }); + + state.update_sovereign_config( + SovereignConfig::default_config_for_test(), + Some(CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN), + ); +} + +/// ### TEST +/// S-FORGE_UPDATE_CONFIG_OK +/// +/// ### ACTION +/// Update sovereign config +/// +/// ### EXPECTED +/// Sovereign config was modified +#[test] +fn test_update_sovereign_config() { + let mut state = SovereignForgeTestState::new(); + + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ])); + + state.finish_setup(); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc.chain_factories(2).is_empty()); + }); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc + .sovereigns_mapper(&OWNER_ADDRESS.to_managed_address()) + .is_empty()); + + assert!(sc.chain_ids().contains(&ManagedBuffer::from(CHAIN_ID))); + + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + assert!(is_chain_config_deployed); + }); + + state.update_sovereign_config(SovereignConfig::new(1, 2, BigUint::default(), None), None); + + let chain_config_address_from_sovereign_forge = state + .get_smart_contract_address_from_sovereign_forge( + ManagedBuffer::from(CHAIN_ID), + ScArray::ChainConfig, + ); + + state + .common_setup + .world + .query() + .to(chain_config_address_from_sovereign_forge) + .whitebox(chain_config::contract_obj, |sc| { + let min_validators = sc.sovereign_config().get().min_validators; + assert!(min_validators == 1); + }) +} + +/// ### TEST +/// S-FORGE_UPDATE_ESDT_SAFE_CONFIG_OK +/// +/// ### ACTION +/// Update ESDT safe config +/// +/// ### EXPECTED +/// ESDT safe config was modified +#[test] +fn test_update_esdt_safe_config() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ])); + + state.finish_setup(); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc.chain_factories(2).is_empty()); + }); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc + .sovereigns_mapper(&OWNER_ADDRESS.to_managed_address()) + .is_empty()); + + assert!(sc.chain_ids().contains(&ManagedBuffer::from(CHAIN_ID))); + + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + assert!(is_chain_config_deployed); + }); + + state + .common_setup + .deploy_header_verifier(vec![ScArray::ESDTSafe]); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_esdt_safe_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + + assert!(is_esdt_safe_deployed); + }); + + state.update_esdt_safe_config( + EsdtSafeConfig { + max_tx_gas_limit: ONE_HUNDRED_THOUSAND.into(), + ..EsdtSafeConfig::default_config() + }, + None, + ); + + let mvx_esdt_safe_address_from_sovereign_forge = state + .get_smart_contract_address_from_sovereign_forge( + ManagedBuffer::from(CHAIN_ID), + ScArray::ESDTSafe, + ); + + state + .common_setup + .world + .query() + .to(mvx_esdt_safe_address_from_sovereign_forge) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + let max_bridged_amount = sc.esdt_safe_config().get().max_tx_gas_limit; + let expected_amount: u64 = ONE_HUNDRED_THOUSAND.into(); + assert!(max_bridged_amount == expected_amount); + }) +} + +/// ### TEST +/// S-FORGE_SET_FEE_OK +/// +/// ### ACTION +/// Set sovereign fee +/// +/// ### EXPECTED +/// The sovereign fee is modified +#[test] +fn test_set_fee() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state.common_setup.deploy_phase_three(None, None); + state.common_setup.deploy_phase_four(None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + let is_header_verifier_deployed = sc + .is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::HeaderVerifier); + let is_esdt_safe_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + let is_fee_market_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::FeeMarket); + + assert!( + is_chain_config_deployed + && is_header_verifier_deployed + && is_esdt_safe_deployed + && is_fee_market_deployed + ); + }); + + let fee_type = FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }; + + let new_fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type, + }; + + state.set_fee(new_fee, None); + + let fee_market_address = state.get_smart_contract_address_from_sovereign_forge( + ManagedBuffer::from(CHAIN_ID), + ScArray::FeeMarket, + ); + + state + .common_setup + .world + .query() + .to(fee_market_address) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(sc.is_fee_enabled()); + assert!(!sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }); +} + +/// ### TEST +/// S-FORGE_SET_FEE_FAIL +/// +/// ### ACTION +/// Call `set_fee()` phase three not completed +/// +/// ### EXPECTED +/// Error FEE_MARKET_NOT_DEPLOYED +#[test] +fn test_set_fee_phase_three_not_completed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + let fee_type = FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }; + + let new_fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type, + }; + + state.set_fee(new_fee, Some(FEE_MARKET_NOT_DEPLOYED)); +} +/// ### TEST +/// S-FORGE_REMOVE_FEE_OK +/// +/// ### ACTION +/// Remove sovereign fee +/// +/// ### EXPECTED +/// The sovereign fee is removed +#[test] +fn test_remove_fee() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + let fee_type = FeeType::Fixed { + token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + per_transfer: BigUint::default(), + per_gas: BigUint::default(), + }; + + let fee = FeeStruct { + base_token: EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN), + fee_type, + }; + state.common_setup.deploy_phase_three(Some(fee), None); + + state.common_setup.deploy_phase_four(None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + let is_header_verifier_deployed = sc + .is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::HeaderVerifier); + let is_esdt_safe_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + let is_fee_market_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::FeeMarket); + + assert!( + is_chain_config_deployed + && is_header_verifier_deployed + && is_esdt_safe_deployed + && is_fee_market_deployed + ); + }); + + state.remove_fee(FIRST_TEST_TOKEN, None); + + let fee_market_address = state.get_smart_contract_address_from_sovereign_forge( + ManagedBuffer::from(CHAIN_ID), + ScArray::FeeMarket, + ); + + state + .common_setup + .world + .query() + .to(fee_market_address) + .whitebox(mvx_fee_market::contract_obj, |sc| { + assert!(!sc.is_fee_enabled()); + assert!(sc + .token_fee(&EgldOrEsdtTokenIdentifier::esdt(FIRST_TEST_TOKEN)) + .is_empty()); + }) +} + +/// ### TEST +/// S-FORGE_REMOVE_FEE_FAIL +/// +/// ### ACTION +/// Call `remove_fee()` when phase three not deployed +/// +/// ### EXPECTED +/// Error FEE_MARKET_NOT_DEPLOYED +#[test] +fn test_remove_fee_phase_three_not_completed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + state.remove_fee(FIRST_TEST_TOKEN, Some(FEE_MARKET_NOT_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_COMPLETE_SETUP_PHASE +/// +/// ### ACTION +/// Call setup_phase() +/// +/// ### EXPECTED +/// Setup phase is completed and set in storage +#[test] +fn test_complete_setup_phase() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + let preferred_chain_id = ManagedBuffer::from(CHAIN_ID); + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(preferred_chain_id.clone()), + OptionalValue::None, + None, + ); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state.common_setup.deploy_phase_three(None, None); + state.common_setup.deploy_phase_four(None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + let is_header_verifier_deployed = sc + .is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::HeaderVerifier); + let is_esdt_safe_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + let is_fee_market_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::FeeMarket); + + assert!( + is_chain_config_deployed + && is_header_verifier_deployed + && is_esdt_safe_deployed + && is_fee_market_deployed + ); + }); + + let mvx_address = state.retrieve_deployed_mvx_esdt_safe_address(preferred_chain_id.clone()); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(mvx_address) + .whitebox(mvx_esdt_safe::contract_obj, |sc| { + sc.native_token() + .set(EgldOrEsdtTokenIdentifier::esdt(NATIVE_TEST_TOKEN)); + }); + + let chain_config_address = + state.retrieve_deployed_chain_config_address(preferred_chain_id.clone()); + + state + .common_setup + .world + .tx() + .from(OWNER_ADDRESS) + .to(chain_config_address) + .typed(ChainConfigContractProxy) + .register(BLSKey::random()) + .payment(MultiEgldOrEsdtPayment::new()) + .returns(ReturnsResultUnmanaged) + .run(); + + state.complete_setup_phase(None); + // NOTE: This will not work until callback fixes + // state.check_setup_phase_completed(preferred_chain_id, true); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_FAIL +/// +/// ### ACTION +/// deploy_phase_one with insufficient cost +/// +/// ### EXPECTED +/// Error DEPLOY_COST_NOT_ENOUGH +#[test] +fn test_deploy_phase_one_deploy_cost_too_low() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(BigUint::from(2u32))); + state.common_setup.deploy_chain_factory(); + state.finish_setup(); + + let deploy_cost = BigUint::from(1u32); + + state.common_setup.deploy_phase_one( + &deploy_cost, + None, + OptionalValue::None, + Some(DEPLOY_COST_NOT_ENOUGH), + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_one twice for same chain_config +/// +/// ### EXPECTED +/// Error CHAIN_CONFIG_ALREADY_DEPLOYED +#[test] +fn test_deploy_phase_one_chain_config_already_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + None, + OptionalValue::None, + Some(CHAIN_CONFIG_ALREADY_DEPLOYED), + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_one wrong chain id format +/// +/// ### EXPECTED +/// Error CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC +#[test] +fn test_deploy_phase_one_preferred_chain_id_not_lowercase_alphanumeric() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from("CHID")), + OptionalValue::None, + Some(CHAIN_ID_NOT_LOWERCASE_ALPHANUMERIC), + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_one wrong chain id length +/// +/// ### EXPECTED +/// Error CHAIN_ID_NOT_FOUR_CHAR_LONG +#[test] +fn test_deploy_phase_one_preferred_chain_id_not_correct_length() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from("CHAINID")), + OptionalValue::None, + Some(INVALID_CHAIN_ID), + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_OK +/// +/// ### ACTION +/// Call deploy_phase_one with no preferred chain id +/// +/// ### EXPECTED +/// Chain-Config is deployed and address is set in storage +#[test] +fn test_deploy_phase_one_no_preferred_chain_id() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc + .sovereigns_mapper(&OWNER_ADDRESS.to_managed_address()) + .is_empty()); + + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + assert!(is_chain_config_deployed); + }) +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_OK +/// +/// ### ACTION +/// Call deploy_phase_one with preferred chain id +/// +/// ### EXPECTED +/// Chain-Config is deployed and address is set in storage +#[test] +fn test_deploy_phase_one_preferred_chain_id() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + assert!(!sc + .sovereigns_mapper(&OWNER_ADDRESS.to_managed_address()) + .is_empty()); + + assert!(sc.chain_ids().contains(&ManagedBuffer::from(CHAIN_ID))); + + let is_chain_config_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ChainConfig); + assert!(is_chain_config_deployed); + }) +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_ONE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_one with an used chain id +/// +/// ### EXPECTED +/// Error CHAIN_ID_ALREADY_IN_USE +#[test] +fn test_deploy_phase_one_with_chain_id_used() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + + state.finish_setup(); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + None, + ); + + state.common_setup.deploy_phase_one( + &DEPLOY_COST.into(), + Some(ManagedBuffer::from(CHAIN_ID)), + OptionalValue::None, + Some(CHAIN_ID_ALREADY_IN_USE), + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_TWO_FAIL +/// +/// ### ACTION +/// Call deploy_phase_two without the first phase +/// +/// ### EXPECTED +/// Error CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN +#[test] +fn test_deploy_phase_two_without_first_phase() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + state.common_setup.deploy_chain_factory(); + state.finish_setup(); + + state.common_setup.deploy_phase_two( + Some(CALLER_DID_NOT_DEPLOY_ANY_SOV_CHAIN), + OptionalValue::None, + ); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_TWO_OK +/// +/// ### ACTION +/// Call deploy_phase_two +/// +/// ### EXPECTED +/// ESDT-Safe is deployed and address is set in the storage +#[test] +fn test_deploy_phase_two() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ])); + state.finish_setup(); + + let deploy_cost = BigUint::from(DEPLOY_COST); + + state + .common_setup + .deploy_phase_one(&deploy_cost, None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + let mut esdt_safe_address_buffer_from_forge = [0u8; 32]; + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_esdt_safe_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + + assert!(is_esdt_safe_deployed); + + esdt_safe_address_buffer_from_forge = sc + .get_contract_address(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe) + .to_byte_array(); + }); + + // NOTE: This will fail since callback inside blackbox don't work + // state + // .common_setup + // .world + // .query() + // .to(ManagedAddress::new_from_bytes( + // &esdt_safe_address_buffer_from_forge, + // )) + // .whitebox(mvx_esdt_safe::contract_obj, |sc| { + // assert!(!sc.state.common_setup.get_native_token()().is_empty()); + // }); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_TWO_FAIL +/// +/// ### ACTION +/// Call deploy_phase_two two times +/// +/// ### EXPECTED +/// Error ESDT_SAFE_ALREADY_DEPLOYED +#[test] +fn test_deploy_phase_two_esdt_safe_already_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + state + .common_setup + .deploy_phase_two(Some(ESDT_SAFE_ALREADY_DEPLOYED), OptionalValue::None); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_THREE_OK +/// +/// ### ACTION +/// Call deploy_phase_three +/// +/// ### EXPECTED +/// Fee-Market is deployed and address is set in storage +#[test] +fn test_deploy_phase_three() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + state.common_setup.deploy_phase_three(None, None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_fee_market_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + + assert!(is_fee_market_deployed); + }) +} + +/// ### TEST +/// S-FORGE_REMOVE_USERS_FROM_FEE_WHITELIST +/// +/// ### ACTION +/// Call remove_users_from_whitelist +/// +/// ### EXPECTED +/// Users are removed from fee-market's storage +#[test] +fn test_remove_users_from_whitelist() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + state.common_setup.deploy_phase_three(None, None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_fee_market_deployed = + sc.is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::ESDTSafe); + + assert!(is_fee_market_deployed); + }); + + let whitelisted_users = vec![ + USER_ADDRESS.to_managed_address(), + OWNER_ADDRESS.to_managed_address(), + ]; + + state.add_users_to_whitelist(whitelisted_users.clone()); + state + .common_setup + .query_user_fee_whitelist(Some(&whitelisted_users)); + + let users_to_remove = vec![USER_ADDRESS.to_managed_address()]; + let expected_users = vec![OWNER_ADDRESS.to_managed_address()]; + + state.remove_users_from_whitelist(users_to_remove.clone()); + state + .common_setup + .query_user_fee_whitelist(Some(&expected_users)); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_THREE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_three without the phase one +/// +/// ### EXPECTED +/// Error ESDT_SAFE_NOT_DEPLOYED +#[test] +fn test_deploy_phase_three_without_phase_one() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + state.deploy_template_scs(Some(vec![ScArray::ChainFactory, ScArray::ChainConfig])); + state.finish_setup(); + + state + .common_setup + .deploy_phase_three(None, Some(ESDT_SAFE_NOT_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_THREE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_three without the phase two +/// +/// ### EXPECTED +/// Error ESDT_SAFE_NOT_DEPLOYED +#[test] +fn test_deploy_phase_three_without_phase_two() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_three(None, Some(ESDT_SAFE_NOT_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_THREE_FAIL +/// +/// ### ACTION +/// Call deploy_phase_three two times +/// +/// ### EXPECTED +/// Error FEE_MARKET_ALREADY_DEPLOYED +#[test] +fn test_deploy_phase_three_already_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state.common_setup.deploy_phase_three(None, None); + state + .common_setup + .deploy_phase_three(None, Some(FEE_MARKET_ALREADY_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_COMPLETE_SETUP_PHASE_FAIL +/// +/// ### ACTION +/// Call complete_setup_phase without phase four deployed +/// +/// ### EXPECTED +/// Error HEADER_VERIFIER_NOT_DEPLOYED +#[test] +fn test_complete_setup_phase_four_not_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + state + .common_setup + .deploy_fee_market(None, ESDT_SAFE_ADDRESS); + state.complete_setup_phase(Some(HEADER_VERIFIER_NOT_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_FOUR_OK +/// +/// ### ACTION +/// Call deploy_phase_four +/// +/// ### EXPECTED +/// Header-Verifier is deployed and address is set in storage +#[test] +fn test_deploy_phase_four() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + + state.common_setup.deploy_phase_three(None, None); + + state.common_setup.deploy_phase_four(None); + + state + .common_setup + .world + .query() + .to(SOVEREIGN_FORGE_SC_ADDRESS) + .whitebox(sovereign_forge::contract_obj, |sc| { + let is_header_verifier_deployed = sc + .is_contract_deployed(&OWNER_ADDRESS.to_managed_address(), ScArray::HeaderVerifier); + + assert!(is_header_verifier_deployed); + }) +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_FOUR_FAIL +/// +/// ### ACTION +/// Call deploy_phase_four without phase three +/// +/// ### EXPECTED +/// Error FEE_MARKET_NOT_DEPLOYED +#[test] +fn test_deploy_phase_four_without_previous_phase() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state + .common_setup + .deploy_phase_four(Some(FEE_MARKET_NOT_DEPLOYED)); +} + +/// ### TEST +/// S-FORGE_DEPLOY_PHASE_FOUR_FAIL +/// +/// ### ACTION +/// Call deploy_phase_four times +/// +/// ### EXPECTED +/// Error HEADER_VERIFIER_ALREADY_DEPLOYED +#[test] +fn test_deploy_phase_four_header_verifier_already_deployed() { + let mut state = SovereignForgeTestState::new(); + state + .common_setup + .deploy_sovereign_forge(OptionalValue::Some(DEPLOY_COST.into())); + + state.deploy_template_scs(Some(vec![ + ScArray::ChainFactory, + ScArray::ChainConfig, + ScArray::ESDTSafe, + ScArray::FeeMarket, + ScArray::HeaderVerifier, + ])); + + state.finish_setup(); + + state + .common_setup + .deploy_phase_one(&DEPLOY_COST.into(), None, OptionalValue::None, None); + state + .common_setup + .deploy_phase_two(None, OptionalValue::None); + state.common_setup.deploy_phase_three(None, None); + state.common_setup.deploy_phase_four(None); + state + .common_setup + .deploy_phase_four(Some(HEADER_VERIFIER_ALREADY_DEPLOYED)); +} diff --git a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.lock b/sovereign-forge/wasm/Cargo.lock similarity index 64% rename from mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.lock rename to sovereign-forge/wasm/Cargo.lock index 0848826ce..6849221ed 100644 --- a/mvx-esdt-safe/wasm-mvx-esdt-safe-full/Cargo.lock +++ b/sovereign-forge/wasm/Cargo.lock @@ -10,56 +10,99 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "chain-config" version = "0.1.0" dependencies = [ + "common-utils", + "cross-chain", + "custom-events", + "error-messages", + "multiversx-sc", + "multiversx-sc-modules", + "proxies", + "setup-phase", + "structs", +] + +[[package]] +name = "chain-factory" +version = "0.1.0" +dependencies = [ + "chain-config", + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", + "proxies", + "structs", +] + +[[package]] +name = "common-utils" +version = "0.1.0" +dependencies = [ + "custom-events", + "error-messages", + "multiversx-sc", + "proxies", + "structs", ] [[package]] name = "cross-chain" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", "multiversx-sc-modules", "proxies", "structs", - "utils", +] + +[[package]] +name = "custom-events" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "structs", ] [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "error-messages" version = "0.1.0" [[package]] -name = "fee-market" +name = "fee-common" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", + "multiversx-sc-modules", "proxies", "structs", - "utils", ] [[package]] @@ -75,10 +118,13 @@ dependencies = [ name = "header-verifier" version = "0.1.0" dependencies = [ + "common-utils", "cross-chain", + "custom-events", "error-messages", "multiversx-sc", "proxies", + "setup-phase", "structs", ] @@ -90,15 +136,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -106,9 +152,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -122,9 +168,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -134,9 +180,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -146,9 +192,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -159,18 +205,18 @@ dependencies = [ [[package]] name = "multiversx-sc-modules" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502edabc05791315e65bddb415086aaaa0f62821ee076a27ddee4d86334e22c7" +checksum = "1872ad788953f3bf6acc7bb7e4ec49ac9939b7aa9fa4cb08c4866e0e2eb71967" dependencies = [ "multiversx-sc", ] [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -180,25 +226,32 @@ name = "mvx-esdt-safe" version = "0.1.0" dependencies = [ "chain-config", + "common-utils", "cross-chain", + "custom-events", "error-messages", - "fee-market", "header-verifier", "multiversx-sc", "multiversx-sc-modules", + "mvx-fee-market", "proxies", "setup-phase", "structs", "testing-sc", - "utils", ] [[package]] -name = "mvx-esdt-safe-full-wasm" -version = "0.0.0" +name = "mvx-fee-market" +version = "0.1.0" dependencies = [ - "multiversx-sc-wasm-adapter", - "mvx-esdt-safe", + "common-utils", + "custom-events", + "error-messages", + "fee-common", + "multiversx-sc", + "proxies", + "setup-phase", + "structs", ] [[package]] @@ -221,9 +274,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -238,18 +291,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -259,15 +312,45 @@ dependencies = [ name = "setup-phase" version = "0.1.0" dependencies = [ + "common-utils", + "custom-events", "error-messages", "multiversx-sc", ] [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sovereign-forge" +version = "0.0.0" +dependencies = [ + "chain-config", + "chain-factory", + "common-utils", + "cross-chain", + "custom-events", + "error-messages", + "fee-common", + "header-verifier", + "multiversx-sc", + "multiversx-sc-modules", + "mvx-esdt-safe", + "mvx-fee-market", + "proxies", + "structs", +] + +[[package]] +name = "sovereign-forge-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "sovereign-forge", +] [[package]] name = "structs" @@ -278,9 +361,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -292,6 +375,7 @@ name = "testing-sc" version = "0.1.0" dependencies = [ "multiversx-sc", + "proxies", ] [[package]] @@ -302,21 +386,12 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "error-messages", - "multiversx-sc", - "structs", -] diff --git a/fee-market/wasm-fee-market/Cargo.toml b/sovereign-forge/wasm/Cargo.toml similarity index 86% rename from fee-market/wasm-fee-market/Cargo.toml rename to sovereign-forge/wasm/Cargo.toml index dd5de5015..52d699a1f 100644 --- a/fee-market/wasm-fee-market/Cargo.toml +++ b/sovereign-forge/wasm/Cargo.toml @@ -5,7 +5,7 @@ # ########################################## [package] -name = "fee-market-wasm" +name = "sovereign-forge-wasm" version = "0.0.0" edition = "2021" publish = false @@ -24,11 +24,11 @@ overflow-checks = false [profile.dev] panic = "abort" -[dependencies.fee-market] +[dependencies.sovereign-forge] path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/sovereign-forge/wasm/src/lib.rs b/sovereign-forge/wasm/src/lib.rs new file mode 100644 index 000000000..3edb3811c --- /dev/null +++ b/sovereign-forge/wasm/src/lib.rs @@ -0,0 +1,55 @@ +// Code generated by the multiversx-sc build system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Upgrade: 1 +// Endpoints: 25 +// Async Callback (empty): 1 +// Promise callbacks: 3 +// Total number of exported functions: 31 + +#![no_std] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + sovereign_forge + ( + init => init + upgrade => upgrade + registerChainFactory => register_chain_factory + registerTrustedToken => register_trusted_token + deployPhaseOne => deploy_phase_one + deployPhaseTwo => deploy_phase_two + deployPhaseThree => deploy_phase_three + deployPhaseFour => deploy_phase_four + completeSetupPhase => complete_setup_phase + getDeployedSovereignContracts => sovereign_deployed_contracts + getTrustedTokens => trusted_tokens + getSovereignSetupPhase => sovereign_setup_phase + getChainFactoryAddress => chain_factories + getDeployCost => deploy_cost + getAllChainIds => chain_ids + updateEsdtSafeConfig => update_esdt_safe_config + updateSovereignConfig => update_sovereign_config + setFee => set_fee + removeFee => remove_fee + addUsersToWhitelist => add_users_to_whitelist + removeUsersFromWhitelist => remove_users_from_whitelist + setTokenBurnMechanism => set_token_burn_mechanism + setTokenLockMechanism => set_token_lock_mechanism + updateDeployCost => update_deploy_cost + pause => pause_endpoint + unpause => unpause_endpoint + isPaused => paused_status + setup_phase => setup_phase + register_deployed_contract => register_deployed_contract + update_configs => update_configs + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/testing-sc/Cargo.lock b/testing-sc/Cargo.lock new file mode 100644 index 000000000..13ef74058 --- /dev/null +++ b/testing-sc/Cargo.lock @@ -0,0 +1,1159 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "multiversx-chain-scenario-format" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcca77966bf5eb9c9f96d0597f17a4fa7b64681cc7b83e39bdf31f8c6ca04d44" +dependencies = [ + "bech32", + "hex", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "sha3", +] + +[[package]] +name = "multiversx-chain-vm" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c537d27dfc7a8e295d4f7c5f67a967ce66b6c3fc8e31a160e8cf28e14ecb2d" +dependencies = [ + "bitflags", + "colored", + "ed25519-dalek", + "hex", + "hex-literal", + "itertools", + "multiversx-chain-vm-executor", + "num-bigint", + "num-traits", + "rand", + "rand_seeder", + "sha2", + "sha3", +] + +[[package]] +name = "multiversx-chain-vm-executor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b59072fa0624b55ae5ae3fa6bfa91515bbeb4ac440214bc4a509e2c8806d6e9f" + +[[package]] +name = "multiversx-sc" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ea89a26f0aacda21437a8ae5ccfbefab99d8191942b3d2eddbcbf84f9866d7" +dependencies = [ + "bitflags", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d7a5a8534e5dc9128cb8f15a65a21dd378e135c6016c7cd1491cd012bc8cb" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", + "num-bigint", + "unwrap-infallible", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dffba1dce273ed5b61ee1b90aeea5c8c744617d0f12624f620768c144d83e753" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c17fdf90fafca2f19085ae67b0502d9f71bf8ab1be3c83808eb88e02a8c18b9" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-meta-lib" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef023806e14a8a6245408c4a7dfdf17d4a1eb39425cedfe50cd2c48f96708ab6" +dependencies = [ + "clap", + "colored", + "convert_case", + "hex", + "lazy_static", + "multiversx-sc", + "rustc_version", + "semver", + "serde", + "serde_json", + "toml", + "wasmparser", + "wasmprinter", +] + +[[package]] +name = "multiversx-sc-scenario" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd16e3bf1f6696d0add36b7b905b1ffdd13b836e584da000f5e265c11e2bfc8d" +dependencies = [ + "base64", + "bech32", + "colored", + "hex", + "itertools", + "log", + "multiversx-chain-scenario-format", + "multiversx-chain-vm", + "multiversx-chain-vm-executor", + "multiversx-sc", + "multiversx-sc-meta-lib", + "num-bigint", + "num-traits", + "pathdiff", + "serde", + "serde_json", + "sha2", + "unwrap-infallible", +] + +[[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-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_seeder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9febe641d2842ffc76ee962668a17578767c4e01735e4802b21ed9a24b2e4e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "testing-sc" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-scenario", + "num-bigint", +] + +[[package]] +name = "testing-sc-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta-lib", + "testing-sc", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unwrap-infallible" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasmparser" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" +dependencies = [ + "ahash", + "bitflags", + "hashbrown 0.14.5", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmprinter" +version = "0.216.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f82916f3892e53620639217d6ec78fe15c678352a3fbf3f3745b6417d0bd70f" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/testing-sc/Cargo.toml b/testing-sc/Cargo.toml index 68d02cddb..177cf0a58 100644 --- a/testing-sc/Cargo.toml +++ b/testing-sc/Cargo.toml @@ -9,10 +9,13 @@ authors = ["you"] path = "src/lib.rs" [dependencies.multiversx-sc] -version = "=0.57.1" +version = "0.63.0" [dev-dependencies] num-bigint = "0.4" [dev-dependencies.multiversx-sc-scenario] -version = "=0.57.1" +version = "0.63.0" + +[dependencies.proxies] +path = "../common/proxies" diff --git a/testing-sc/meta/Cargo.toml b/testing-sc/meta/Cargo.toml index 65106fb9d..1b6114943 100644 --- a/testing-sc/meta/Cargo.toml +++ b/testing-sc/meta/Cargo.toml @@ -8,5 +8,5 @@ publish = false path = ".." [dependencies.multiversx-sc-meta-lib] -version = "=0.57.1" +version = "0.63.0" default-features = false diff --git a/testing-sc/sc-config.toml b/testing-sc/sc-config.toml index 4232fc6f1..042dd469b 100644 --- a/testing-sc/sc-config.toml +++ b/testing-sc/sc-config.toml @@ -1,16 +1,2 @@ -[contracts.main] -name = "testing-sc" - -[contracts.full] -name = "testing-sc-full" -add-unlabelled = true -add-labels = ["testing-sc-external-view"] - -[contracts.view] -name = "testing-view" -external-view = true -add-unlabelled = false -add-labels = ["testing-sc-external-view"] - [[proxy]] path = "../common/proxies/src/testing_sc_proxy.rs" diff --git a/testing-sc/src/lib.rs b/testing-sc/src/lib.rs index c77ccca13..c995d4668 100644 --- a/testing-sc/src/lib.rs +++ b/testing-sc/src/lib.rs @@ -2,6 +2,7 @@ #[allow(unused_imports)] use multiversx_sc::imports::*; +use proxies::mvx_esdt_safe_proxy::MvxEsdtSafeProxy; #[multiversx_sc::contract] pub trait TestingSc { @@ -16,4 +17,29 @@ pub trait TestingSc { fn hello(&self, value: BigUint) { require!(value > BigUint::zero(), "Value should be greater than 0") } + + #[endpoint] + fn read_native_token(&self, wanted_address: ManagedAddress) { + self.tx() + .to(&wanted_address) + .typed(MvxEsdtSafeProxy) + .native_token() + .async_call_and_exit(); + } + + #[endpoint] + fn send_tokens(&self, token_id: EgldOrEsdtTokenIdentifier, nonce: u64, amount: BigUint) { + let self_address = self.blockchain().get_sc_address(); + let receiver = self.blockchain().get_caller(); + + let sc_balance = self.blockchain().get_esdt_balance( + &self_address, + &token_id.clone().unwrap_esdt(), + nonce, + ); + require!(sc_balance >= amount, "Insufficient balance"); + + self.send() + .direct_esdt(&receiver, &token_id.unwrap_esdt(), nonce, &amount); + } } diff --git a/testing-sc/wasm-testing-sc-full/Cargo.lock b/testing-sc/wasm-testing-sc-full/Cargo.lock deleted file mode 100644 index eb55505d3..000000000 --- a/testing-sc/wasm-testing-sc-full/Cargo.lock +++ /dev/null @@ -1,216 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "testing-sc" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "testing-sc-full-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "testing-sc", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" diff --git a/testing-sc/wasm-testing-sc-full/Cargo.toml b/testing-sc/wasm-testing-sc-full/Cargo.toml deleted file mode 100644 index d45a704cd..000000000 --- a/testing-sc/wasm-testing-sc-full/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "testing-sc-full-wasm" -version = "0.0.0" -edition = "2021" -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.testing-sc] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" - -[workspace] -members = ["."] diff --git a/testing-sc/wasm-testing-sc/src/lib.rs b/testing-sc/wasm-testing-sc/src/lib.rs deleted file mode 100644 index 77d113d28..000000000 --- a/testing-sc/wasm-testing-sc/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Upgrade: 1 -// Endpoints: 1 -// Async Callback (empty): 1 -// Total number of exported functions: 4 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::endpoints! { - testing_sc - ( - init => init - upgrade => upgrade - hello => hello - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/testing-sc/wasm-testing-view/Cargo.lock b/testing-sc/wasm-testing-view/Cargo.lock deleted file mode 100644 index 17268a6fa..000000000 --- a/testing-sc/wasm-testing-view/Cargo.lock +++ /dev/null @@ -1,216 +0,0 @@ -# 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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "generic-array" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703" -dependencies = [ - "typenum", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "multiversx-chain-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" -dependencies = [ - "bitflags", - "multiversx-sc-codec", -] - -[[package]] -name = "multiversx-sc" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" -dependencies = [ - "bitflags", - "generic-array", - "hex-literal", - "multiversx-chain-core", - "multiversx-sc-codec", - "multiversx-sc-derive", - "num-traits", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" -dependencies = [ - "arrayvec", - "bitflags", - "multiversx-sc-codec-derive", - "unwrap-infallible", -] - -[[package]] -name = "multiversx-sc-codec-derive" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "multiversx-sc-derive" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" -dependencies = [ - "hex", - "proc-macro2", - "quote", - "radix_trie", - "syn", -] - -[[package]] -name = "multiversx-sc-wasm-adapter" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" -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.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "testing-sc" -version = "0.1.0" -dependencies = [ - "multiversx-sc", -] - -[[package]] -name = "testing-view-wasm" -version = "0.0.0" -dependencies = [ - "multiversx-sc-wasm-adapter", - "testing-sc", -] - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unwrap-infallible" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" diff --git a/testing-sc/wasm-testing-view/Cargo.toml b/testing-sc/wasm-testing-view/Cargo.toml deleted file mode 100644 index fff496fd2..000000000 --- a/testing-sc/wasm-testing-view/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Code generated by the multiversx-sc build system. DO NOT EDIT. - -# ########################################## -# ############## AUTO-GENERATED ############# -# ########################################## - -[package] -name = "testing-view-wasm" -version = "0.0.0" -edition = "2021" -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.testing-sc] -path = ".." - -[dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" - -[workspace] -members = ["."] diff --git a/testing-sc/wasm-testing-view/src/lib.rs b/testing-sc/wasm-testing-view/src/lib.rs deleted file mode 100644 index 4b0720f86..000000000 --- a/testing-sc/wasm-testing-view/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by the multiversx-sc build system. DO NOT EDIT. - -//////////////////////////////////////////////////// -////////////////// AUTO-GENERATED ////////////////// -//////////////////////////////////////////////////// - -// Init: 1 -// Endpoints: 0 -// Async Callback (empty): 1 -// Total number of exported functions: 2 - -#![no_std] - -multiversx_sc_wasm_adapter::allocator!(); -multiversx_sc_wasm_adapter::panic_handler!(); - -multiversx_sc_wasm_adapter::external_view_init! {} - -multiversx_sc_wasm_adapter::external_view_endpoints! { - testing_sc - ( - ) -} - -multiversx_sc_wasm_adapter::async_callback_empty! {} diff --git a/testing-sc/wasm-testing-sc/Cargo.lock b/testing-sc/wasm/Cargo.lock similarity index 70% rename from testing-sc/wasm-testing-sc/Cargo.lock rename to testing-sc/wasm/Cargo.lock index db18754cb..769f8fc68 100644 --- a/testing-sc/wasm-testing-sc/Cargo.lock +++ b/testing-sc/wasm/Cargo.lock @@ -10,21 +10,21 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "endian-type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "generic-array" @@ -43,15 +43,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "multiversx-chain-core" -version = "0.14.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f1f0526ca094185847776c2c03de97ea082743ee4e6f799860ad67e7d78250" +checksum = "8f972b4ede5c4e138062c4b83f364545928abf4e92630d1a7382f912fdd95058" dependencies = [ "bitflags", "multiversx-sc-codec", @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "multiversx-sc" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20602d4fc336092faadc03b6b445c4bb5e4dbc4c86fb53726e46eca67070813a" +checksum = "ddd27483e7afddf9e1af168f6fa81f94da474d1a92f673c9636839e9504a0a4d" dependencies = [ "bitflags", "generic-array", @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e0f24a020adf32faffae1c57475c6b7285a3216d1a36e4a73617639721c2e0" +checksum = "70685641eede17be7d3c678bc5ce4dc7ffeb100cf18399cdcdcdf52482cf98a5" dependencies = [ "arrayvec", "bitflags", @@ -87,9 +87,9 @@ dependencies = [ [[package]] name = "multiversx-sc-codec-derive" -version = "0.22.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f17d5ee23657d5cddc7f4471925b5a49f7e25306f8bba656ea6917acc629d" +checksum = "ce73df2ee4376ab524981f980126aba16f395386014207eb6e9efd9ff6015d62" dependencies = [ "hex", "proc-macro2", @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "multiversx-sc-derive" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d44d08d3376c199e8c391cc07752d2822cd289693da5319d7905599501e557f" +checksum = "f1fae2b34423c1a4691d61b1e0e58e07bb56058281a5625efb9ca4ba2210fbc9" dependencies = [ "hex", "proc-macro2", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "multiversx-sc-wasm-adapter" -version = "0.57.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8253b84600554fbe16ea0a234b009854348dc95ce174ba6eba046dc5918f8f" +checksum = "942aecd63d0acde1294fda62119fbbfd558bd55fb6ec207466c550a6aaa5a61e" dependencies = [ "multiversx-sc", ] @@ -139,27 +139,35 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] +[[package]] +name = "proxies" +version = "0.1.0" +dependencies = [ + "multiversx-sc", + "structs", +] + [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", @@ -167,15 +175,22 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "structs" +version = "0.1.0" +dependencies = [ + "multiversx-sc", +] [[package]] name = "syn" -version = "2.0.100" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -187,6 +202,7 @@ name = "testing-sc" version = "0.1.0" dependencies = [ "multiversx-sc", + "proxies", ] [[package]] @@ -205,9 +221,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unwrap-infallible" diff --git a/testing-sc/wasm-testing-sc/Cargo.toml b/testing-sc/wasm/Cargo.toml similarity index 96% rename from testing-sc/wasm-testing-sc/Cargo.toml rename to testing-sc/wasm/Cargo.toml index 99c8d2af5..536570b30 100644 --- a/testing-sc/wasm-testing-sc/Cargo.toml +++ b/testing-sc/wasm/Cargo.toml @@ -28,7 +28,7 @@ panic = "abort" path = ".." [dependencies.multiversx-sc-wasm-adapter] -version = "=0.57.1" +version = "0.63.0" [workspace] members = ["."] diff --git a/testing-sc/wasm-testing-sc-full/src/lib.rs b/testing-sc/wasm/src/lib.rs similarity index 79% rename from testing-sc/wasm-testing-sc-full/src/lib.rs rename to testing-sc/wasm/src/lib.rs index 77d113d28..997de64cc 100644 --- a/testing-sc/wasm-testing-sc-full/src/lib.rs +++ b/testing-sc/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 1 +// Endpoints: 3 // Async Callback (empty): 1 -// Total number of exported functions: 4 +// Total number of exported functions: 6 #![no_std] @@ -21,6 +21,8 @@ multiversx_sc_wasm_adapter::endpoints! { init => init upgrade => upgrade hello => hello + read_native_token => read_native_token + send_tokens => send_tokens ) }