From c3822f2e639e123f2bb094ab9d1cc8f1962c3ec6 Mon Sep 17 00:00:00 2001 From: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:44:43 +0200 Subject: [PATCH] Reverse Splitter service (#49) * Implement splitter service - WIP * Implement pre_validate func on ServiceConfig * Implement calculations for ratios * Fix broken test * Add test for a mix of 3 tokens incl. a CW20 and ratios * cargo fmt * Add README * Remove splitter-temp service * Add support for config update + tests * Added test; fix build * Reverse splitter service * Add support for dynamic ratio * Use ServiceAccountType for account addresses in ServiceConfig * Address PR review feedback; add zero amount & ratio checks * Simplify Reverse Splitter service config * Address PR review feedback --- Cargo.lock | 507 +++++----- Cargo.toml | 21 +- .../reverse-splitter-temp/.cargo/config.toml | 4 - .../services/reverse-splitter-temp/Cargo.toml | 37 - .../services/reverse-splitter-temp/README.md | 3 - .../reverse-splitter-temp/src/bin/schema.rs | 11 - .../reverse-splitter-temp/src/contract.rs | 144 --- .../reverse-splitter-temp/src/error.rs | 20 - .../reverse-splitter-temp/src/helpers.rs | 11 - .../services/reverse-splitter-temp/src/lib.rs | 7 - .../services/reverse-splitter-temp/src/msg.rs | 95 -- .../reverse-splitter-temp/src/state.rs | 7 - .../services/reverse-splitter/Cargo.toml | 37 + contracts/services/reverse-splitter/README.md | 77 ++ .../reverse-splitter/src/bin/schema.rs | 14 + .../services/reverse-splitter/src/contract.rs | 301 ++++++ .../services/reverse-splitter/src/lib.rs | 5 + .../services/reverse-splitter/src/msg.rs | 426 ++++++++ .../services/reverse-splitter/src/tests.rs | 907 ++++++++++++++++++ .../testing/test-dynamic-ratio/Cargo.toml | 5 +- packages/service-utils/src/testing.rs | 24 + workflow-manager/Cargo.toml | 12 +- workflow-manager/src/service.rs | 2 +- 23 files changed, 2076 insertions(+), 601 deletions(-) delete mode 100644 contracts/services/reverse-splitter-temp/.cargo/config.toml delete mode 100644 contracts/services/reverse-splitter-temp/Cargo.toml delete mode 100644 contracts/services/reverse-splitter-temp/README.md delete mode 100644 contracts/services/reverse-splitter-temp/src/bin/schema.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/contract.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/error.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/helpers.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/lib.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/msg.rs delete mode 100644 contracts/services/reverse-splitter-temp/src/state.rs create mode 100644 contracts/services/reverse-splitter/Cargo.toml create mode 100644 contracts/services/reverse-splitter/README.md create mode 100644 contracts/services/reverse-splitter/src/bin/schema.rs create mode 100644 contracts/services/reverse-splitter/src/contract.rs create mode 100644 contracts/services/reverse-splitter/src/lib.rs create mode 100644 contracts/services/reverse-splitter/src/msg.rs create mode 100644 contracts/services/reverse-splitter/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index d86ea9d..5937ee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "ark-bls12-381" @@ -233,14 +233,14 @@ dependencies = [ [[package]] name = "astroport" -version = "5.3.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3b225e86694e2fd1adbf230581bce1690a871585aec9edd64b022a313a8493" +checksum = "079959b98988f2adcf8b884f8dd536d70c5306e231b1b047dc7c0f68800f6da3" dependencies = [ "astroport-circular-buffer", "cosmos-sdk-proto 0.19.0", - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-asset", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -256,17 +256,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31c7369d3c4126804f861620db2221c15b5fa7b7718f12180e265b087c933fb6" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 0.15.1", "thiserror", ] [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -275,24 +275,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -308,9 +308,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -534,18 +534,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.1.18" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -706,8 +706,8 @@ dependencies = [ "bip39", "cosmos-sdk-proto 0.21.1", "cosmrs 0.16.0", - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "enum-repr", "injective-protobuf", "prost 0.12.6", @@ -798,15 +798,15 @@ dependencies = [ [[package]] name = "cosmwasm-core" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490" +checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa" [[package]] name = "cosmwasm-crypto" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f862b355f7e47711e0acfe6af92cb3fd8fd5936b66a9eaa338b51edabd1e77d" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" dependencies = [ "digest 0.10.7", "ed25519-zebra 3.1.0", @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d" +checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538" dependencies = [ "ark-bls12-381", "ark-ec", @@ -840,31 +840,31 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd85de6467cd1073688c86b39833679ae6db18cf4771471edd9809f15f1679f1" +checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-derive" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424" +checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "cosmwasm-schema" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b4cd28147a66eba73720b47636a58097a979ad8c8bfdb4ed437ebcbfe362576" +checksum = "93d388adfa9cb449557a92e9318121ac1a481fc4f599213b03a5b62699b403b4" dependencies = [ - "cosmwasm-schema-derive 1.5.7", + "cosmwasm-schema-derive 1.5.8", "schemars", "serde", "serde_json", @@ -873,11 +873,11 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc0d4d85e83438ab9a0fea9348446f7268bc016aacfebce37e998559f151294" +checksum = "f86b4d949b6041519c58993a73f4bbfba8083ba14f7001eae704865a09065845" dependencies = [ - "cosmwasm-schema-derive 2.1.3", + "cosmwasm-schema-derive 2.1.4", "schemars", "serde", "serde_json", @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acd45c63d41bc9b16bc6dc7f6bd604a8c2ad29ce96c8f3c96d7fc8ef384392e" +checksum = "2411b389e56e6484f81ba955b758d02522d620c98fc960c4bd2251d48b7aa19f" dependencies = [ "proc-macro2", "quote", @@ -897,26 +897,26 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf5c8adac41bb7751c050d7c4c18675be19ee128714454454575e894424eeef" +checksum = "c8ef1b5835a65fcca3ab8b9a02b4f4dacc78e233a5c2f20b270efb9db0666d12" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "cosmwasm-std" -version = "1.5.7" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" +checksum = "c21fde95ccd20044a23c0ac6fd8c941f3e8c158169dc94b5aa6491a2d9551a8d" dependencies = [ "base64 0.21.7", "bech32 0.9.1", "bnum 0.10.0", - "cosmwasm-crypto 1.5.7", - "cosmwasm-derive 1.5.7", + "cosmwasm-crypto 1.5.8", + "cosmwasm-derive 1.5.8", "derivative", "forward_ref", "hex", @@ -930,16 +930,16 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1" +checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b" dependencies = [ "base64 0.22.1", "bech32 0.11.0", "bnum 0.11.0", "cosmwasm-core", - "cosmwasm-crypto 2.1.3", - "cosmwasm-derive 2.1.3", + "cosmwasm-crypto 2.1.4", + "cosmwasm-derive 2.1.4", "derive_more", "hex", "rand_core 0.6.4", @@ -1050,7 +1050,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1072,7 +1072,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" dependencies = [ - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", ] [[package]] @@ -1081,7 +1081,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73553ee4dad5b1678977ff603e72c3fdd41518ca2b0bd9b245b21e4c72eafa9e" dependencies = [ - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", ] [[package]] @@ -1090,8 +1090,8 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c999a12f8cd8736f6f86e9a4ede5905530cb23cfdef946b9da1c506ad1b70799" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-address-like 1.0.4", "cw-storage-plus 1.2.0", "cw20 1.1.2", @@ -1103,8 +1103,8 @@ name = "cw-denom" version = "2.4.2" source = "git+https://github.com/DA0-DA0/dao-contracts?branch=cw-std-2#48ce9249ebd28c9092de0bf2368c78458726c9e9" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw20 2.0.0", "thiserror", ] @@ -1117,12 +1117,12 @@ checksum = "b0ae276e7a06ad1b7e7da78a3d68aba80634cde30ee7fe8259a94e653603fef8" dependencies = [ "anyhow", "bech32 0.11.0", - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", "derivative", "itertools 0.13.0", - "prost 0.13.2", + "prost 0.13.3", "schemars", "serde", "sha2 0.10.8", @@ -1135,8 +1135,8 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2f8ee96ac5342c795a0610410998fc075a95af8c796b6d16479cdffd2471f1" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-address-like 2.0.0", "cw-ownable-derive", "cw-storage-plus 2.0.0", @@ -1161,7 +1161,7 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" dependencies = [ - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", "schemars", "serde", ] @@ -1172,7 +1172,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", "schemars", "serde", ] @@ -1183,7 +1183,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "schemars", "serde", ] @@ -1194,8 +1194,8 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw2 1.1.2", "schemars", "semver", @@ -1209,8 +1209,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "schemars", "serde", "thiserror", @@ -1222,8 +1222,8 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 1.2.0", "schemars", "semver", @@ -1237,8 +1237,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b04852cd38f044c0751259d5f78255d07590d136b8a86d4e09efdd7666bd6d27" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "schemars", "semver", @@ -1252,8 +1252,8 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-utils 1.0.3", "schemars", "serde", @@ -1265,8 +1265,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a42212b6bf29bbdda693743697c621894723f35d3db0d5df930be22903d0e27c" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-utils 2.0.0", "schemars", "serde", @@ -1278,8 +1278,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6de8c32e100f1fca306972d86b617234a5e6b00594ea2b48716fd6804d4d95d" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "cw2 2.0.0", "cw20 2.0.0", @@ -1310,7 +1310,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1321,7 +1321,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1386,7 +1386,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1396,7 +1396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1416,7 +1416,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "unicode-xid", ] @@ -1825,7 +1825,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1891,7 +1891,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1929,7 +1929,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1964,6 +1964,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heapsize" version = "0.4.2" @@ -2048,9 +2054,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -2179,12 +2185,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -2193,7 +2199,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a52219a08aba8c17846fd23d472d1d69c817fe5b427d135273e4c7311edd6972" dependencies = [ - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", "ethereum-types", "num", "protobuf 2.28.0", @@ -2209,7 +2215,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2721d8c2fed1fd1dff4cd6d119711a74acf27a6eeea6bf09cd44d192119e52ea" dependencies = [ - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "itertools 0.10.5", "proc-macro2", "quote", @@ -2283,9 +2289,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -2318,9 +2324,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" @@ -2348,8 +2354,8 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" name = "local-interchaintest" version = "0.1.0" dependencies = [ - "cosmwasm-std 1.5.7", - "cosmwasm-std 2.1.3", + "cosmwasm-std 1.5.8", + "cosmwasm-std 2.1.4", "cw-utils 2.0.0", "env_logger 0.11.5", "hex", @@ -2373,9 +2379,9 @@ dependencies = [ [[package]] name = "localic-std" version = "0.0.1" -source = "git+https://github.com/strangelove-ventures/interchaintest?branch=main#3ca97c474ca55078ff3cc9c0ed85232f28bead30" +source = "git+https://github.com/strangelove-ventures/interchaintest?branch=main#9ac38ccbef082d59502c7803d9f4cc8eaaedc92b" dependencies = [ - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", "reqwest", "serde_json", "thiserror", @@ -2388,7 +2394,7 @@ version = "0.1.0" source = "git+https://github.com/timewave-computer/localic-utils?branch=main#e2801c6549a7e33599e6c8e5363a4f22942d37fd" dependencies = [ "astroport", - "cosmwasm-std 1.5.7", + "cosmwasm-std 1.5.8", "derive_builder", "localic-std", "log", @@ -2417,12 +2423,12 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "margined-neutron-std" -version = "4.2.0" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372e28877f65a613648757bf665586a570c621e9eb7a5da5bd99171d55f7db9f" +checksum = "5240db561faabe0918d01d517758786955075e18d4095bef5fbe91db79faf663" dependencies = [ "chrono", - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "injective-std-derive", "prost 0.12.6", "prost-types 0.12.6", @@ -2507,8 +2513,8 @@ checksum = "8fcd104aa99d8078ee0ece835c3ee919f9972dc5295c741c08f4dd77093f9df8" dependencies = [ "bech32 0.9.1", "cosmos-sdk-proto 0.20.0", - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "prost 0.12.6", "prost-types 0.12.6", "protobuf 3.3.0", @@ -2523,14 +2529,14 @@ dependencies = [ [[package]] name = "neutron-test-tube" -version = "4.2.0" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79bc03a4c5cf20ec2d2ebfd90d869bbc8e766674ea090c63bb8198bc269f1b6" +checksum = "80526b0e02d9b8e6cc3790d0252161def3fb4d025975ec600e561bce315d8468" dependencies = [ "base64 0.21.7", "bindgen", "cosmrs 0.15.0", - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "hex", "margined-neutron-std", "prost 0.12.6", @@ -2618,7 +2624,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2672,9 +2678,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -2705,7 +2714,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2846,9 +2855,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -2857,9 +2866,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -2867,22 +2876,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -2906,7 +2915,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2933,17 +2942,17 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polytone" version = "1.1.0" source = "git+https://github.com/DA0-DA0/polytone?rev=f70440a#f70440a35f12f97a9018849ca7e6d241a53582ce" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 1.2.0", "thiserror", ] @@ -2953,8 +2962,8 @@ name = "polytone-note" version = "1.1.0" source = "git+https://github.com/DA0-DA0/polytone?rev=f70440a#f70440a35f12f97a9018849ca7e6d241a53582ce" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2967,8 +2976,8 @@ name = "polytone-proxy" version = "1.1.0" source = "git+https://github.com/DA0-DA0/polytone?rev=f70440a#f70440a35f12f97a9018849ca7e6d241a53582ce" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2981,8 +2990,8 @@ name = "polytone-voice" version = "1.1.0" source = "git+https://github.com/DA0-DA0/polytone?rev=f70440a#f70440a35f12f97a9018849ca7e6d241a53582ce" dependencies = [ - "cosmwasm-schema 1.5.7", - "cosmwasm-std 1.5.7", + "cosmwasm-schema 1.5.8", + "cosmwasm-std 1.5.8", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2992,6 +3001,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3035,7 +3050,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3069,12 +3084,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.13.2", + "prost-derive 0.13.3", ] [[package]] @@ -3100,20 +3115,20 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3277,18 +3292,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -3298,9 +3313,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3309,9 +3324,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3452,9 +3467,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -3557,7 +3572,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3605,9 +3620,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3672,7 +3687,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3683,7 +3698,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3716,14 +3731,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -3911,7 +3926,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3924,7 +3939,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3961,9 +3976,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -3999,9 +4014,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -4190,7 +4205,7 @@ checksum = "ea84026a246e45e873cd5fe17be69df7adce2029748b27e8ad60390a9f4f5360" dependencies = [ "base64 0.21.7", "cosmrs 0.15.0", - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "prost 0.12.6", "serde", "serde_json", @@ -4206,22 +4221,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4339,7 +4354,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4418,11 +4433,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -4507,7 +4522,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4559,9 +4574,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -4589,9 +4604,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -4610,15 +4625,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -4653,8 +4668,8 @@ checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" name = "valence-account-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-utils 2.0.0", @@ -4671,8 +4686,8 @@ dependencies = [ name = "valence-astroport-lper" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-ownable", "cw20 2.0.0", "neutron-test-tube", @@ -4687,8 +4702,8 @@ dependencies = [ name = "valence-astroport-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "neutron-test-tube", ] @@ -4696,8 +4711,8 @@ dependencies = [ name = "valence-astroport-withdrawer" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-ownable", "cw20 2.0.0", "neutron-test-tube", @@ -4712,8 +4727,8 @@ dependencies = [ name = "valence-authorization" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-ownable", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", @@ -4736,8 +4751,8 @@ dependencies = [ name = "valence-authorization-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-ownable", "cw-utils 2.0.0", "neutron-sdk", @@ -4749,8 +4764,8 @@ dependencies = [ name = "valence-base-account" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-denom", "cw-multi-test", "cw-ownable", @@ -4771,8 +4786,8 @@ dependencies = [ name = "valence-forwarder-service" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", @@ -4794,8 +4809,8 @@ dependencies = [ name = "valence-macros" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "proc-macro2", "quote", "syn 1.0.109", @@ -4805,16 +4820,16 @@ dependencies = [ name = "valence-polytone-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", ] [[package]] name = "valence-processor" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", "cw2 2.0.0", @@ -4829,8 +4844,8 @@ dependencies = [ name = "valence-processor-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", "serde", @@ -4840,29 +4855,34 @@ dependencies = [ ] [[package]] -name = "valence-reverse-splitter" +name = "valence-reverse-splitter-service" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", - "cw2 2.0.0", + "cw-utils 2.0.0", + "cw20 2.0.0", + "cw20-base", + "getset", "schemars", "serde", + "sha2 0.10.8", "thiserror", - "valence-account-utils", "valence-macros", + "valence-service-base", "valence-service-utils", + "valence-test-dynamic-ratio", ] [[package]] name = "valence-service-base" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", @@ -4878,8 +4898,8 @@ dependencies = [ name = "valence-service-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-denom", "cw-multi-test", "cw-ownable", @@ -4899,8 +4919,8 @@ dependencies = [ name = "valence-splitter-service" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", @@ -4924,8 +4944,8 @@ dependencies = [ name = "valence-template-service" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", @@ -4943,15 +4963,18 @@ dependencies = [ name = "valence-test-dynamic-ratio" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", "cw-utils 2.0.0", + "cw20 2.0.0", + "cw20-base", "getset", "schemars", "serde", + "sha2 0.10.8", "thiserror", "valence-macros", "valence-service-base", @@ -4962,8 +4985,8 @@ dependencies = [ name = "valence-test-service" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-storage-plus 2.0.0", "thiserror", "valence-processor-utils", @@ -4973,8 +4996,8 @@ dependencies = [ name = "valence-workflow-registry" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-multi-test", "cw-ownable", "cw-storage-plus 2.0.0", @@ -4989,8 +5012,8 @@ dependencies = [ name = "valence-workflow-registry-utils" version = "0.1.0" dependencies = [ - "cosmwasm-schema 2.1.3", - "cosmwasm-std 2.1.3", + "cosmwasm-schema 2.1.4", + "cosmwasm-std 2.1.4", "cw-ownable", "serde", "serde_json", @@ -5061,7 +5084,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -5095,7 +5118,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5315,9 +5338,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -5342,7 +5365,7 @@ dependencies = [ "bech32 0.11.0", "config", "cosmos-grpc-client", - "cosmwasm-std 2.1.3", + "cosmwasm-std 2.1.4", "cw-ownable", "dashmap", "dhat", @@ -5366,7 +5389,7 @@ dependencies = [ "valence-macros", "valence-processor", "valence-processor-utils", - "valence-reverse-splitter", + "valence-reverse-splitter-service", "valence-service-base", "valence-service-utils", "valence-splitter-service", @@ -5401,7 +5424,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5421,5 +5444,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index bed3e33..b6bab47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,17 +49,16 @@ sha2 = "0.10.8" thiserror = "1.0.63" # our contracts -valence-authorization = { path = "contracts/authorization", features = ["library"] } -valence-base-account = { path = "contracts/accounts/base_account", features = ["library"] } -valence-processor = { path = "contracts/processor", features = ["library"] } -valence-reverse-splitter = { path = "contracts/services/reverse-splitter-temp", features = ["library"] } -valence-splitter = { path = "contracts/services/splitter-temp", features = ["library"] } -valence-workflow-registry = { path = "contracts/workflow-registry", features = ["library"] } -valence-astroport-withdrawer = { path = "contracts/services/astroport-withdrawer", features = ["library"] } -valence-astroport-lper = { path = "contracts/services/astroport-lper", features = ["library"] } -valence-splitter-service = { path = "contracts/services/splitter", features = ["library"] } -valence-test-dynamic-ratio = { path = "contracts/testing/test-dynamic-ratio", features = ["library"] } -valence-test-service = { path = "contracts/testing/test-service", features = ["library"] } +valence-authorization = { path = "contracts/authorization", features = ["library"] } +valence-base-account = { path = "contracts/accounts/base_account", features = ["library"] } +valence-processor = { path = "contracts/processor", features = ["library"] } +valence-workflow-registry = { path = "contracts/workflow-registry", features = ["library"] } +valence-astroport-lper = { path = "contracts/services/astroport-lper", features = ["library"] } +valence-astroport-withdrawer = { path = "contracts/services/astroport-withdrawer", features = ["library"] } +valence-reverse-splitter-service = { path = "contracts/services/reverse-splitter", features = ["library"] } +valence-splitter-service = { path = "contracts/services/splitter", features = ["library"] } +valence-test-dynamic-ratio = { path = "contracts/testing/test-dynamic-ratio", features = ["library"] } +valence-test-service = { path = "contracts/testing/test-service", features = ["library"] } # our packages valence-account-utils = { path = "packages/account-utils" } diff --git a/contracts/services/reverse-splitter-temp/.cargo/config.toml b/contracts/services/reverse-splitter-temp/.cargo/config.toml deleted file mode 100644 index af5698e..0000000 --- a/contracts/services/reverse-splitter-temp/.cargo/config.toml +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --bin schema" diff --git a/contracts/services/reverse-splitter-temp/Cargo.toml b/contracts/services/reverse-splitter-temp/Cargo.toml deleted file mode 100644 index 4649b47..0000000 --- a/contracts/services/reverse-splitter-temp/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "valence-reverse-splitter" -version = "0.1.0" -authors = ["Art3mix "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.16.0 -""" - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -valence-macros = { workspace = true } -cw-ownable = { workspace = true } -valence-account-utils = { workspace = true } -valence-service-utils = { workspace = true } - -[dev-dependencies] -cw-multi-test = { workspace = true } diff --git a/contracts/services/reverse-splitter-temp/README.md b/contracts/services/reverse-splitter-temp/README.md deleted file mode 100644 index 37b64ef..0000000 --- a/contracts/services/reverse-splitter-temp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Splitter Service - -This is a splitter service, it allows for splits from 1 input account into many accounts. diff --git a/contracts/services/reverse-splitter-temp/src/bin/schema.rs b/contracts/services/reverse-splitter-temp/src/bin/schema.rs deleted file mode 100644 index 4f3e9ce..0000000 --- a/contracts/services/reverse-splitter-temp/src/bin/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; - -use valence_reverse_splitter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } -} diff --git a/contracts/services/reverse-splitter-temp/src/contract.rs b/contracts/services/reverse-splitter-temp/src/contract.rs deleted file mode 100644 index fe5a7bd..0000000 --- a/contracts/services/reverse-splitter-temp/src/contract.rs +++ /dev/null @@ -1,144 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use cw2::set_contract_version; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{CONFIG, PROCESSOR}; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:base_service"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - cw_ownable::initialize_owner(deps.storage, deps.api, Some(&msg.owner))?; - PROCESSOR.save(deps.storage, &deps.api.addr_validate(&msg.processor)?)?; - - let config = msg.config.validate(deps.as_ref())?; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new() - .add_attribute("method", "instantiate") - .add_attribute("admin", msg.owner)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::UpdateProcessor { processor } => { - cw_ownable::is_owner(deps.storage, &info.sender)?; - PROCESSOR.save(deps.storage, &deps.api.addr_validate(&processor)?)?; - Ok(Response::default()) - } - ExecuteMsg::UpdateOwnership(action) => { - cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; - Ok(Response::default()) - } - ExecuteMsg::UpdateConfig { new_config } => execute::update_config(deps, info, new_config), - ExecuteMsg::Processor(action_msg) => actions::handle_action(deps, env, info, action_msg), - } -} - -mod execute { - use cosmwasm_std::{DepsMut, MessageInfo, Response}; - - use crate::{msg::OptionalServiceConfig, ContractError}; - - pub fn update_config( - deps: DepsMut, - info: MessageInfo, - new_config: OptionalServiceConfig, - ) -> Result { - cw_ownable::is_owner(deps.storage, &info.sender)?; - - new_config.update_config(deps)?; - - Ok(Response::new() - .add_attribute("method", "update_config") - .add_attribute("updated_by", info.sender)) - } -} - -mod actions { - use cosmwasm_std::{ - to_json_binary, BankMsg, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, - }; - - use crate::{helpers::is_processor, msg::ActionsMsgs, state::CONFIG, ContractError}; - - pub fn handle_action( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ActionsMsgs, - ) -> Result { - is_processor(&deps, &info)?; - - match msg { - ActionsMsgs::Split {} => { - let config = CONFIG.load(deps.storage)?; - let mut messages: Vec = vec![]; - - config.splits.iter().try_for_each(|(denom, split)| { - // TODO: change split to be percentage and not amounts - messages.extend( - split - .iter() - .map(|(addr, amount)| { - let bank_msg = BankMsg::Send { - to_address: config.output_addr.to_string(), - amount: vec![Coin { - denom: denom.clone(), - amount: *amount, - }], - }; - - Ok(WasmMsg::Execute { - contract_addr: addr.to_string()?, - msg: to_json_binary( - &valence_account_utils::msg::ExecuteMsg::ExecuteMsg { - msgs: vec![bank_msg.into()], - }, - )?, - funds: vec![], - } - .into()) - }) - .collect::, ContractError>>()?, - ); - - Ok::<(), ContractError>(()) - })?; - - Ok(Response::new() - .add_messages(messages) - .add_attribute("method", "split")) - } - } - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetAdmin {} => to_json_binary(&cw_ownable::get_ownership(deps.storage)?), - QueryMsg::GetServiceConfig {} => to_json_binary(&CONFIG.load(deps.storage)?), - } -} - -#[cfg(test)] -mod tests {} diff --git a/contracts/services/reverse-splitter-temp/src/error.rs b/contracts/services/reverse-splitter-temp/src/error.rs deleted file mode 100644 index 9643143..0000000 --- a/contracts/services/reverse-splitter-temp/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use cosmwasm_std::StdError; -use cw_ownable::OwnershipError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error(transparent)] - OwnershipError(#[from] OwnershipError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Unauthorized, Not the processor")] - NotProcessor, - // Add any other custom errors you like here. - // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. -} diff --git a/contracts/services/reverse-splitter-temp/src/helpers.rs b/contracts/services/reverse-splitter-temp/src/helpers.rs deleted file mode 100644 index f4e2e14..0000000 --- a/contracts/services/reverse-splitter-temp/src/helpers.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_std::{DepsMut, MessageInfo}; - -use crate::{state::PROCESSOR, ContractError}; - -pub fn is_processor(deps: &DepsMut, info: &MessageInfo) -> Result<(), ContractError> { - let processor = PROCESSOR.load(deps.storage)?; - if info.sender != processor { - return Err(ContractError::NotProcessor); - } - Ok(()) -} diff --git a/contracts/services/reverse-splitter-temp/src/lib.rs b/contracts/services/reverse-splitter-temp/src/lib.rs deleted file mode 100644 index 233dbf5..0000000 --- a/contracts/services/reverse-splitter-temp/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod contract; -mod error; -pub mod helpers; -pub mod msg; -pub mod state; - -pub use crate::error::ContractError; diff --git a/contracts/services/reverse-splitter-temp/src/msg.rs b/contracts/services/reverse-splitter-temp/src/msg.rs deleted file mode 100644 index 3d08851..0000000 --- a/contracts/services/reverse-splitter-temp/src/msg.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Deps, DepsMut, Uint128}; -use cw_ownable::cw_ownable_execute; -use valence_macros::OptionalStruct; -use valence_service_utils::{ServiceAccountType, ServiceConfigInterface}; - -use crate::{state::CONFIG, ContractError}; - -#[cw_serde] -pub struct InstantiateMsg { - pub owner: String, - pub processor: String, - pub config: ServiceConfig, -} - -#[cw_ownable_execute] -#[cw_serde] -pub enum ExecuteMsg { - UpdateProcessor { processor: String }, - UpdateConfig { new_config: OptionalServiceConfig }, - Processor(ActionsMsgs), -} - -#[cw_serde] -pub enum ActionsMsgs { - Split {}, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Addr)] - GetAdmin {}, - #[returns(ServiceConfig)] - GetServiceConfig {}, -} - -#[cw_serde] -#[derive(OptionalStruct)] -pub struct ServiceConfig { - /// Address we send funds to - output_addr: ServiceAccountType, - splits: SplitsConfig, -} - -impl ServiceConfig { - pub fn validate(&self, deps: Deps) -> Result { - // TODO: Verify splits are valid - Ok(Config { - output_addr: self.output_addr.to_addr(deps.api)?, - splits: self.splits.clone(), - }) - } -} - -impl ServiceConfigInterface for ServiceConfig { - fn is_diff(&self, other: &ServiceConfig) -> bool { - !self.eq(other) - } -} - -impl OptionalServiceConfig { - /// TODO: (2) Implement the update_config function to update config - /// Field list matches the fields in the ServiceConfig struct, but all of them are optional - /// if a field is Some, it means we want to update it. - /// You can return here anything the service needs - pub fn update_config(self, deps: DepsMut) -> Result<(), ContractError> { - let mut config = CONFIG.load(deps.storage)?; - - if let Some(output_addr) = self.output_addr { - config.output_addr = output_addr.to_addr(deps.api)?; - } - - if let Some(splits) = self.splits { - // TODO: Verify splits are valid - config.splits = splits; - } - - CONFIG.save(deps.storage, &config)?; - Ok(()) - } -} - -#[cw_serde] -pub struct Config { - pub output_addr: Addr, - pub splits: SplitsConfig, -} - -/// Splits is a list of denoms, -/// where each of the denom has a list of addresses and amount how much to send to that addres - -pub type SplitsConfig = BTreeMap>; diff --git a/contracts/services/reverse-splitter-temp/src/state.rs b/contracts/services/reverse-splitter-temp/src/state.rs deleted file mode 100644 index cfb7080..0000000 --- a/contracts/services/reverse-splitter-temp/src/state.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cosmwasm_std::Addr; -use cw_storage_plus::Item; - -use crate::msg::Config; - -pub const PROCESSOR: Item = Item::new("processor"); -pub const CONFIG: Item = Item::new("config"); diff --git a/contracts/services/reverse-splitter/Cargo.toml b/contracts/services/reverse-splitter/Cargo.toml new file mode 100644 index 0000000..a2f138c --- /dev/null +++ b/contracts/services/reverse-splitter/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "valence-reverse-splitter-service" +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +version = { workspace = true } +repository = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-ownable = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +getset = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +valence-macros = { workspace = true } +valence-service-utils = { workspace = true } +valence-service-base = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } +cw-ownable = { workspace = true } +cw20 = { workspace = true } +cw20-base = { workspace = true } +sha2 = { workspace = true } +valence-service-utils = { workspace = true, features = ["testing"] } +valence-test-dynamic-ratio = { workspace = true } diff --git a/contracts/services/reverse-splitter/README.md b/contracts/services/reverse-splitter/README.md new file mode 100644 index 0000000..2215f47 --- /dev/null +++ b/contracts/services/reverse-splitter/README.md @@ -0,0 +1,77 @@ +# Valence Reverse Splitter service + +The **Reverse Splitter** service allows to **route funds** from **one or more input account(s)** to a **single output account**, for **one or more token denom(s)** according to the configured **ratio(s)**. It is typically used as part of a **Valence Workflow**. In that context, a **Processor** contract will be the main contract interacting with the Forwarder service. + +## High-level flow + +```mermaid +--- +title: Reverse Splitter Service +--- +graph LR + IA1((Input + Account1)) + IA2((Input + Account2)) + OA((Output + Account)) + P[Processor] + S[Reverse Splitter + Service] + C[Contract] + P -- 1/Split --> S + S -- 2/Query balances --> IA1 + S -- 2'/Query balances --> IA2 + S -. 3/Query split ratio .-> C + S -- 4/Do Send funds --> IA1 + S -- 4'/Do Send funds --> IA2 + IA1 -- 5/Send funds --> OA + IA2 -- 5'/Send funds --> OA +``` + +## Configuration + +The service is configured on instantiation via the `ServiceConfig` type. +```rust +struct ServiceConfig { + output_addr: ServiceAccountType, // Account to which the funds are sent. + splits: Vec, // Split configuration per denom. + base_denom: UncheckedDenom // Base denom, used with ratios. +} + +// Split config for specified account +struct UncheckedSplitConfig { + denom: UncheckedDenom, // Denom for this split configuration (either native or CW20). + account: ServiceAccountType, // Address of the input account for this split config. + amount: UncheckedSplitAmount, // Fixed amount of tokens or an amount defined based on a ratio. + factor: Option // Multiplier relative to other denoms (only used if a ratio is specified). +} + +// Ratio configuration, either fixed & dynamically calculated +enum UncheckedRatioConfig { + FixedAmount(Uint128), // Fixed amount of tokens + FixedRatio(Decimal), // Fixed ratio e.g. 0.0262 for NTRN/STARS (or could be another arbitrary ratio) + DynamicRatio { // Dynamic ratio calculation (delegated to external contract) + contract_addr: "", + params: "base64-encoded arbitrary payload to send in addition to the denoms" + } +} + +// Standard query & response for contract computing a dynamic ratio +// for the Splitter & Reverse Splitter services. +#[cw_serde] +#[derive(QueryResponses)] +pub enum DynamicRatioQueryMsg { + #[returns(DynamicRatioResponse)] + DynamicRatio { + denoms: Vec, + params: String, + } +} + +#[cw_serde] +// Response returned by the external contract for a dynamic ratio +struct DynamicRatioResponse { + pub denom_ratios: HashMap, +} +``` diff --git a/contracts/services/reverse-splitter/src/bin/schema.rs b/contracts/services/reverse-splitter/src/bin/schema.rs new file mode 100644 index 0000000..71af0e3 --- /dev/null +++ b/contracts/services/reverse-splitter/src/bin/schema.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::write_api; + +use valence_reverse_splitter_service::msg::{ + ActionMsgs, OptionalServiceConfig, QueryMsg, ServiceConfig, +}; +use valence_service_utils::msg::{ExecuteMsg, InstantiateMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/services/reverse-splitter/src/contract.rs b/contracts/services/reverse-splitter/src/contract.rs new file mode 100644 index 0000000..7e1a75d --- /dev/null +++ b/contracts/services/reverse-splitter/src/contract.rs @@ -0,0 +1,301 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use valence_service_utils::{ + error::ServiceError, + msg::{ExecuteMsg, InstantiateMsg}, +}; + +use crate::msg::{ActionMsgs, Config, OptionalServiceConfig, QueryMsg, ServiceConfig}; + +// version info for migration info +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + valence_service_base::instantiate(deps, CONTRACT_NAME, CONTRACT_VERSION, msg) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + valence_service_base::execute( + deps, + env, + info, + msg, + actions::process_action, + execute::update_config, + ) +} + +mod actions { + use std::collections::{hash_map::Entry, HashMap}; + + use cosmwasm_std::{ + Addr, CosmosMsg, Decimal, DepsMut, Empty, Env, Fraction, MessageInfo, QuerierWrapper, + Response, StdResult, Uint128, + }; + + use valence_service_utils::{ + denoms::CheckedDenom, + error::ServiceError, + execute_on_behalf_of, + msg::{DynamicRatioQueryMsg, DynamicRatioResponse}, + }; + + use crate::msg::{ActionMsgs, Config, SplitAmount}; + + pub fn process_action( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ActionMsgs, + cfg: Config, + ) -> Result { + match msg { + ActionMsgs::Split {} => { + // Determine the amounts to transfer per split config + let transfer_amounts = prepare_transfer_amounts(&cfg, &deps.querier)?; + + // Prepare messages to send the coins to the output account + let transfer_messages = + prepare_transfer_messages(transfer_amounts, cfg.output_addr())?; + + // Wrap the transfer messages to be executed on behalf of the input account + let input_account_msgs = transfer_messages + .into_iter() + .map(|(msg, account)| execute_on_behalf_of(vec![msg], account)) + .collect::>>()?; + + Ok(Response::new() + .add_attribute("method", "split") + .add_messages(input_account_msgs)) + } + } + } + + // Prepare transfer messages for each denom + fn prepare_transfer_messages<'a, I>( + coins_to_transfer: I, + output_addr: &Addr, + ) -> Result, ServiceError> + where + I: IntoIterator< + Item = ( + cosmwasm_std::Uint128, + &'a valence_service_utils::denoms::CheckedDenom, + &'a Addr, + ), + >, + { + let transfer_messages = coins_to_transfer + .into_iter() + .map(|(amount, denom, account)| { + denom + .get_transfer_to_message(output_addr, amount) + .map(|msg| (msg, account)) + }) + .collect::>>()?; + Ok(transfer_messages) + } + + fn prepare_transfer_amounts<'a>( + cfg: &'a Config, + querier: &QuerierWrapper, + ) -> Result< + Vec<( + cosmwasm_std::Uint128, + &'a valence_service_utils::denoms::CheckedDenom, + &'a Addr, + )>, + ServiceError, + > { + // Get input account balances for each denom (one balance query per denom) + let mut account_balances: HashMap = HashMap::new(); + // Dynamic ratios + let mut dynamic_ratios: HashMap = HashMap::new(); + let mut denom_amount_count = 0; + + for split in cfg.splits() { + let denom = split.denom(); + let key = account_key(split.account(), denom); + // Query account/denom balance and add it to cache + let balance = denom.query_balance(querier, split.account())?; + account_balances.insert(key, balance); + + match split.amount() { + SplitAmount::FixedAmount(amount) => { + // Stop if the specified amount is greater than the input account's balance + if *amount > balance { + return Err(ServiceError::ExecutionError(format!( + "Insufficient balance on account {} for denom '{:?}' in split config (required: {}, available: {}).", + split.account(), denom, amount, balance, + ))); + } + denom_amount_count += 1; + } + SplitAmount::DynamicRatio { + contract_addr, + params, + } => { + let key = dyn_ratio_key(denom, contract_addr, params); + if let Entry::Vacant(e) = dynamic_ratios.entry(key) { + let ratio = query_dynamic_ratio(querier, contract_addr, params, denom)?; + e.insert(ratio); + } + } + _ => {} + } + } + + if denom_amount_count == cfg.splits().len() { + // If all splits have an amount (and we have checked the balances), + // we can return the amounts as-is. + return cfg + .splits() + .iter() + .map(|split| match split.amount() { + SplitAmount::FixedAmount(amount) => { + Ok((*amount, split.denom(), split.account())) + } + _ => unreachable!(), + }) + .collect::, _>>(); + } + + // If not all splits have an amount, we need to compute the amounts based on the ratios + + // Prepare transfer messages for each split config + let amounts_in_base_denom = cfg + .splits() + .iter() + .map(|split| { + // Lookup account/denom balance + let account = split.account(); + let denom = split.denom(); + let balance = account_balances.get(&account_key(account, denom)).unwrap(); + let (amount, ratio, factor) = if let SplitAmount::FixedAmount(amount) = + split.amount() + { + (*amount, Decimal::one(), &None::) + } else { + let ratio = match split.amount() { + SplitAmount::FixedRatio(ratio) => *ratio, + SplitAmount::DynamicRatio { + contract_addr, + params, + } => *dynamic_ratios + .get(&dyn_ratio_key(denom, contract_addr, params)) + .unwrap(), + _ => unreachable!(), + }; + let mut amount = balance.multiply_ratio(ratio.numerator(), ratio.denominator()); + if let Some(factor) = split.factor() { + amount = amount + .checked_div((*factor as u128).into()) + .map_err(|err| ServiceError::Std(err.into()))?; + } + (amount, ratio, split.factor()) + }; + Ok((amount, ratio, factor, denom, account)) + }) + .collect::, ServiceError>>()?; + + // Find the minimum amount in base denom + let min_amount_in_base_denom = *amounts_in_base_denom + .iter() + .map(|(amount, _, _, _, _)| amount) + .min() + .unwrap(); + + amounts_in_base_denom + .into_iter() + .map(|(_, ratio, factor, denom, account)| { + // Divide min amount by ratio (invert ratio's numerator and denominator and multiply) + let mut amount = + min_amount_in_base_denom.multiply_ratio(ratio.denominator(), ratio.numerator()); + if let Some(factor) = factor { + amount = amount + .checked_mul((*factor as u128).into()) + .map_err(|err| ServiceError::Std(err.into()))?; + } + Ok((amount, denom, account)) + }) + .collect::, _>>() + } + + fn query_dynamic_ratio( + querier: &QuerierWrapper, + contract_addr: &Addr, + params: &str, + denom: &CheckedDenom, + ) -> Result { + let denom_name = denom.to_string(); + let res: DynamicRatioResponse = querier.query_wasm_smart( + contract_addr, + &DynamicRatioQueryMsg::DynamicRatio { + denoms: vec![denom_name.clone()], + params: params.to_string(), + }, + )?; + res.denom_ratios + .get(&denom_name) + .copied() + .ok_or(ServiceError::ExecutionError(format!( + "Dynamic ratio not found for denom '{}'.", + denom + ))) + } + + fn account_key(account: &Addr, denom: &CheckedDenom) -> String { + format!("{}/{:?}", account, denom) + } + + fn dyn_ratio_key(denom: &CheckedDenom, contract_addr: &Addr, params: &str) -> String { + format!("{:?}-{}/{}", denom, contract_addr, params) + } +} + +mod execute { + use cosmwasm_std::{DepsMut, Env, MessageInfo}; + use valence_service_utils::error::ServiceError; + + use crate::msg::{Config, OptionalServiceConfig}; + + pub fn update_config( + deps: &DepsMut, + _env: Env, + _info: MessageInfo, + config: &mut Config, + new_config: OptionalServiceConfig, + ) -> Result<(), ServiceError> { + new_config.update_config(deps, config) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Ownership {} => { + to_json_binary(&valence_service_base::get_ownership(deps.storage)?) + } + QueryMsg::GetProcessor {} => { + to_json_binary(&valence_service_base::get_processor(deps.storage)?) + } + QueryMsg::GetServiceConfig {} => { + let config: Config = valence_service_base::load_config(deps.storage)?; + to_json_binary(&config) + } + } +} diff --git a/contracts/services/reverse-splitter/src/lib.rs b/contracts/services/reverse-splitter/src/lib.rs new file mode 100644 index 0000000..08d6d68 --- /dev/null +++ b/contracts/services/reverse-splitter/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod msg; + +#[cfg(test)] +mod tests; diff --git a/contracts/services/reverse-splitter/src/msg.rs b/contracts/services/reverse-splitter/src/msg.rs new file mode 100644 index 0000000..d562e3a --- /dev/null +++ b/contracts/services/reverse-splitter/src/msg.rs @@ -0,0 +1,426 @@ +use std::collections::HashSet; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal, Deps, DepsMut, Uint128}; +use cw_ownable::cw_ownable_query; +use getset::{Getters, Setters}; +use valence_macros::OptionalStruct; +use valence_service_utils::denoms::CheckedDenom; +use valence_service_utils::{ + denoms::UncheckedDenom, error::ServiceError, msg::ServiceConfigValidation, +}; +use valence_service_utils::{ServiceAccountType, ServiceConfigInterface}; + +#[cw_serde] +pub enum ActionMsgs { + Split {}, +} + +#[cw_ownable_query] +#[cw_serde] +#[derive(QueryResponses)] +/// Enum representing the different query messages that can be sent. +pub enum QueryMsg { + /// Query to get the processor address. + #[returns(Addr)] + GetProcessor {}, + /// Query to get the service configuration. + #[returns(Config)] + GetServiceConfig {}, +} + +pub type SplitConfigs = Vec; + +#[cw_serde] +#[derive(Getters, Setters)] +pub struct SplitConfig { + #[getset(get = "pub", set)] + denom: CheckedDenom, + #[getset(get = "pub", set)] + account: Addr, + #[getset(get = "pub", set)] + amount: SplitAmount, + #[getset(get = "pub", set)] + factor: Option, +} + +impl SplitConfig { + pub fn new( + denom: CheckedDenom, + account: Addr, + amount: SplitAmount, + factor: Option, + ) -> Self { + SplitConfig { + denom, + account, + amount, + factor, + } + } +} + +#[cw_serde] +pub enum SplitAmount { + FixedAmount(Uint128), + FixedRatio(Decimal), + DynamicRatio { contract_addr: Addr, params: String }, +} + +#[cw_serde] +pub enum UncheckedSplitAmount { + FixedAmount(Uint128), + FixedRatio(Decimal), + DynamicRatio { + contract_addr: String, + params: String, + }, +} + +#[cw_serde] +pub struct UncheckedSplitConfig { + pub denom: UncheckedDenom, + pub account: ServiceAccountType, + pub amount: UncheckedSplitAmount, + pub factor: Option, +} + +impl UncheckedSplitConfig { + pub fn new( + denom: UncheckedDenom, + account: impl Into, + amount: UncheckedSplitAmount, + factor: Option, + ) -> Self { + UncheckedSplitConfig { + denom, + account: account.into(), + amount, + factor, + } + } + + pub fn with_native_amount(amount: u128, denom: &str, input: &Addr) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Native(denom.to_string()), + input, + UncheckedSplitAmount::FixedAmount(amount.into()), + None, + ) + } + + pub fn with_cw20_amount(amount: u128, addr: &Addr, input: &Addr) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Cw20(addr.to_string()), + input, + UncheckedSplitAmount::FixedAmount(amount.into()), + None, + ) + } + + pub fn with_native_ratio(ratio: Decimal, denom: &str, input: &Addr) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Native(denom.to_string()), + input, + UncheckedSplitAmount::FixedRatio(ratio), + None, + ) + } + + pub fn with_cw20_ratio(ratio: Decimal, addr: &Addr, input: &Addr) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Cw20(addr.to_string()), + input, + UncheckedSplitAmount::FixedRatio(ratio), + None, + ) + } + + pub fn with_native_dyn_ratio( + contract_addr: &Addr, + params: &str, + denom: &str, + input: &Addr, + ) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Native(denom.to_string()), + input, + UncheckedSplitAmount::DynamicRatio { + contract_addr: contract_addr.to_string(), + params: params.to_string(), + }, + None, + ) + } + + pub fn with_cw20_dyn_ratio( + contract_addr: &Addr, + params: &str, + addr: &Addr, + input: &Addr, + ) -> Self { + UncheckedSplitConfig::new( + UncheckedDenom::Cw20(addr.to_string()), + input, + UncheckedSplitAmount::DynamicRatio { + contract_addr: contract_addr.to_string(), + params: params.to_string(), + }, + None, + ) + } + + pub fn set_factor(mut self, factor: u64) -> Self { + self.factor = Some(factor); + self + } +} + +#[allow(dead_code)] +struct DynamicRatioResponse { + ratio: Uint128, +} + +#[cw_serde] +#[derive(OptionalStruct)] +pub struct ServiceConfig { + pub output_addr: ServiceAccountType, + pub splits: Vec, + pub base_denom: UncheckedDenom, +} + +impl ServiceConfig { + pub fn new( + output_addr: impl Into, + splits: Vec, + base_denom: UncheckedDenom, + ) -> Self { + ServiceConfig { + output_addr: output_addr.into(), + splits, + base_denom, + } + } + + fn do_validate(&self, api: &dyn cosmwasm_std::Api) -> Result { + let output_addr = self.output_addr.to_addr(api)?; + validate_splits(api, &self.splits, &self.base_denom)?; + Ok(output_addr) + } +} + +impl ServiceConfigValidation for ServiceConfig { + #[cfg(not(target_arch = "wasm32"))] + fn pre_validate(&self, api: &dyn cosmwasm_std::Api) -> Result<(), ServiceError> { + self.do_validate(api)?; + Ok(()) + } + + fn validate(&self, deps: Deps) -> Result { + let output_addr = self.do_validate(deps.api)?; + // Convert the unchecked denoms to checked denoms + let checked_splits = convert_to_checked_configs(deps, &self.splits)?; + let base_denom = self + .base_denom + .clone() + .into_checked(deps) + .map_err(|err| ServiceError::ConfigurationError(err.to_string()))?; + + Ok(Config { + output_addr, + splits: checked_splits, + base_denom, + }) + } +} + +fn convert_to_checked_configs( + deps: Deps<'_>, + splits: &[UncheckedSplitConfig], +) -> Result, ServiceError> { + splits + .iter() + .map(|c| { + let denom = c + .denom + .clone() + .into_checked(deps) + .map_err(|err| ServiceError::ConfigurationError(err.to_string()))?; + let account = c.account.to_addr(deps.api)?; + let amount = convert_to_checked_split_amount(deps.api, &c.amount)?; + + Ok(SplitConfig { + denom, + account, + amount, + factor: c.factor, + }) + }) + .collect() +} + +fn convert_to_checked_split_amount( + api: &dyn cosmwasm_std::Api, + amount: &UncheckedSplitAmount, +) -> Result { + match amount { + UncheckedSplitAmount::FixedAmount(a) => Ok(SplitAmount::FixedAmount(*a)), + UncheckedSplitAmount::FixedRatio(r) => Ok(SplitAmount::FixedRatio(*r)), + UncheckedSplitAmount::DynamicRatio { + contract_addr, + params, + } => Ok(SplitAmount::DynamicRatio { + contract_addr: api.addr_validate(contract_addr)?, + params: params.clone(), + }), + } +} + +impl ServiceConfigInterface for ServiceConfig { + /// This function is used to see if 2 configs are different + fn is_diff(&self, other: &ServiceConfig) -> bool { + !self.eq(other) + } +} + +impl OptionalServiceConfig { + pub fn update_config(self, deps: &DepsMut, config: &mut Config) -> Result<(), ServiceError> { + // First update output_addr & base_denom (if needed) + if let Some(output_addr) = self.output_addr { + config.output_addr = output_addr.to_addr(deps.api)?; + } + + if let Some(base_denom) = self.base_denom.clone() { + config.base_denom = base_denom + .into_checked(deps.as_ref()) + .map_err(|err| ServiceError::ConfigurationError(err.to_string()))?; + } + + // Then validate & update splits (if needed) + if let Some(splits) = self.splits { + validate_splits( + deps.api, + &splits, + // Use the new base_denom if it was updated, or the existing one (as unchecked) + &self.base_denom.unwrap_or(match &config.base_denom { + CheckedDenom::Native(denom) => UncheckedDenom::Native(denom.clone()), + CheckedDenom::Cw20(addr) => UncheckedDenom::Cw20(addr.to_string()), + }), + )?; + + config.splits = convert_to_checked_configs(deps.as_ref(), &splits)?; + } + Ok(()) + } +} + +fn validate_splits( + api: &dyn cosmwasm_std::Api, + splits: &Vec, + base_denom: &UncheckedDenom, +) -> Result<(), ServiceError> { + if splits.is_empty() { + return Err(ServiceError::ConfigurationError( + "No split configuration provided.".to_string(), + )); + } + + let mut denom_set = HashSet::new(); + let mut config_has_ratio = false; + let mut config_has_amount_for_non_base_denom = false; + for split in splits { + split.account.to_addr(api)?; + // Note: can't validate denom without the deps + + // Ensure splits are unique in split configs + let key = format!("{:?}|{:?}", split.denom, split.account); + if !denom_set.insert(key) { + return Err(ServiceError::ConfigurationError(format!( + "Duplicate split '{:?}|{:?}' in split config.", + split.denom, split.account + ))); + } + + match &split.amount { + UncheckedSplitAmount::FixedAmount(amount) => { + if amount == Uint128::zero() { + return Err(ServiceError::ConfigurationError( + "Invalid split config: amount cannot be zero.".to_string(), + )); + } + + if split.factor.is_some() { + return Err(ServiceError::ConfigurationError( + "Invalid split config: a factor cannot be specified with an amount." + .to_string(), + )); + } + + if split.denom != *base_denom { + config_has_amount_for_non_base_denom = true; + } + } + UncheckedSplitAmount::FixedRatio(ratio) => { + if ratio == Decimal::zero() { + return Err(ServiceError::ConfigurationError( + "Invalid split config: ratio cannot be zero.".to_string(), + )); + } + if split.denom == *base_denom && ratio != Decimal::one() { + return Err(ServiceError::ConfigurationError( + "Invalid split config: fixed ratio for base denom must be 1.".to_string(), + )); + } + config_has_ratio = true; + } + UncheckedSplitAmount::DynamicRatio { contract_addr, .. } => { + api.addr_validate(contract_addr)?; + if split.denom == *base_denom { + return Err(ServiceError::ConfigurationError( + "Invalid split config: ratio for base denom cannot be a dynamic one." + .to_string(), + )); + } + config_has_ratio = true; + } + } + + if let Some(factor) = split.factor { + if factor == 0 { + return Err(ServiceError::ConfigurationError( + "Invalid split config: factor cannot be zero.".to_string(), + )); + } + } + } + + // If there are ratios, we only allow an amount to be set for the base denom + if config_has_ratio && config_has_amount_for_non_base_denom { + return Err(ServiceError::ConfigurationError( + "Invalid split config: only base denom can have an amount when ratios are specified for other some denoms.".to_string(), + )); + } + + Ok(()) +} + +#[cw_serde] +#[derive(Getters, Setters)] +pub struct Config { + #[getset(get = "pub", set)] + output_addr: Addr, + #[getset(get = "pub", set)] + splits: SplitConfigs, + #[getset(get = "pub", set)] + base_denom: CheckedDenom, +} + +impl Config { + pub fn new(output_addr: Addr, splits: SplitConfigs, base_denom: CheckedDenom) -> Self { + Config { + output_addr, + splits, + base_denom, + } + } +} diff --git a/contracts/services/reverse-splitter/src/tests.rs b/contracts/services/reverse-splitter/src/tests.rs new file mode 100644 index 0000000..0e3b983 --- /dev/null +++ b/contracts/services/reverse-splitter/src/tests.rs @@ -0,0 +1,907 @@ +use crate::msg::{ + ActionMsgs, Config, QueryMsg, ServiceConfig, SplitAmount, SplitConfig, UncheckedSplitConfig, +}; +use cosmwasm_std::{Addr, Decimal}; +use cw20::Cw20Coin; +use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; +use cw_ownable::Ownership; +use getset::{Getters, Setters}; +use valence_service_utils::{ + denoms::{CheckedDenom, UncheckedDenom}, + msg::ExecuteMsg, + msg::InstantiateMsg, + testing::{ServiceTestSuite, ServiceTestSuiteBase}, +}; + +const NTRN: &str = "untrn"; +const STARS: &str = "ustars"; +const MEME: &str = "umeme"; +const CATZ: &str = "ucatz"; +const ZERO: u128 = 0u128; +const ONE_MILLION: u128 = 1_000_000_000_000_u128; +const TEN_MILLION: u128 = 10_000_000_000_000_u128; +const HUNDRED_MILLION: u128 = 100_000_000_000_000_u128; +const HALF_MILLION: u128 = ONE_MILLION / 2; +const FIVE_MILLION: u128 = TEN_MILLION / 2; + +#[derive(Getters, Setters)] +struct ReverseSplitterTestSuite { + #[getset(get)] + inner: ServiceTestSuiteBase, + #[getset(get)] + reverse_splitter_code_id: u64, + #[getset(get)] + dyn_ratio_code_id: u64, + #[getset(get)] + output_addr: Addr, +} + +impl Default for ReverseSplitterTestSuite { + fn default() -> Self { + Self::new() + } +} + +#[allow(dead_code)] +impl ReverseSplitterTestSuite { + pub fn new() -> Self { + let mut inner = ServiceTestSuiteBase::new(); + + let output_addr = inner.app().api().addr_make("output_account"); + + // Forwarder contract + let reverse_splitter_code = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + + let reverse_splitter_code_id = inner.app_mut().store_code(Box::new(reverse_splitter_code)); + + let dyn_ratio_code = ContractWrapper::new( + valence_test_dynamic_ratio::contract::execute, + valence_test_dynamic_ratio::contract::instantiate, + valence_test_dynamic_ratio::contract::query, + ); + + let dyn_ratio_code_id = inner.app_mut().store_code(Box::new(dyn_ratio_code)); + + Self { + inner, + reverse_splitter_code_id, + dyn_ratio_code_id, + output_addr, + } + } + + pub fn reverse_splitter_init(&mut self, cfg: &ServiceConfig) -> Addr { + let init_msg = InstantiateMsg { + owner: self.owner().to_string(), + processor: self.processor().to_string(), + config: cfg.clone(), + }; + let addr = self.contract_init(self.reverse_splitter_code_id, "splitter", &init_msg, &[]); + + cfg.splits.iter().for_each(|split| { + self.account_approve_service( + split.account.to_addr(self.api()).unwrap(), + addr.to_string(), + ) + .unwrap(); + }); + + addr + } + + pub fn dyn_ratio_contract_init(&mut self, denom: &str, ratio: Decimal) -> Addr { + let init_msg = valence_test_dynamic_ratio::msg::InstantiateMsg { + denom_ratios: [(denom.to_string(), ratio)].into(), + }; + self.contract_init(self.dyn_ratio_code_id, "dynamic_ratio", &init_msg, &[]) + } + + fn reverse_splitter_config( + &self, + splits: Vec, + base_denom: UncheckedDenom, + ) -> ServiceConfig { + ServiceConfig::new(self.output_addr(), splits, base_denom) + } + + fn cw20_token_init( + &mut self, + name: &str, + symbol: &str, + initial_balances: Vec<(u128, String)>, + ) -> Addr { + self.cw20_init( + name, + symbol, + 6, + initial_balances + .into_iter() + .map(|(amount, address)| Cw20Coin { + address, + amount: amount.into(), + }) + .collect(), + ) + } + + fn execute_split(&mut self, addr: Addr) -> AnyResult { + self.contract_execute( + addr, + &ExecuteMsg::<_, ServiceConfig>::ProcessAction(ActionMsgs::Split {}), + ) + } + + fn update_config(&mut self, addr: Addr, new_config: ServiceConfig) -> AnyResult { + let owner = self.owner().clone(); + self.app_mut().execute_contract( + owner, + addr, + &ExecuteMsg::::UpdateConfig { new_config }, + &[], + ) + } +} + +impl ServiceTestSuite for ReverseSplitterTestSuite { + fn app(&self) -> &App { + self.inner.app() + } + + fn app_mut(&mut self) -> &mut App { + self.inner.app_mut() + } + + fn owner(&self) -> &Addr { + self.inner.owner() + } + + fn processor(&self) -> &Addr { + self.inner.processor() + } + + fn account_code_id(&self) -> u64 { + self.inner.account_code_id() + } + + fn cw20_code_id(&self) -> u64 { + self.inner.cw20_code_id() + } +} + +#[test] +fn instantiate_with_valid_single_split() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = suite.account_init("input_account", vec![]); + + let cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_native_amount( + ONE_MILLION, + NTRN, + &input_addr, + )], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Verify owner + let owner_res: Ownership = suite.query_wasm(&svc, &QueryMsg::Ownership {}); + assert_eq!(owner_res.owner, Some(suite.owner().clone())); + + // Verify processor + let processor_addr: Addr = suite.query_wasm(&svc, &QueryMsg::GetProcessor {}); + assert_eq!(processor_addr, suite.processor().clone()); + + // Verify service config + let svc_cfg: Config = suite.query_wasm(&svc, &QueryMsg::GetServiceConfig {}); + assert_eq!( + svc_cfg, + Config::new( + suite.output_addr().clone(), + vec![SplitConfig::new( + CheckedDenom::Native(NTRN.into()), + input_addr, + SplitAmount::FixedAmount(ONE_MILLION.into()), + None, + )], + CheckedDenom::Native(NTRN.into()) + ) + ); +} + +#[test] +#[should_panic(expected = "Configuration error: No split configuration provided.")] +fn instantiate_fails_for_no_split_config() { + let mut suite = ReverseSplitterTestSuite::default(); + + // Configure reverse splitter with no split config + let cfg = suite.reverse_splitter_config(vec![], UncheckedDenom::Native(NTRN.into())); + + // Instantiate Reverse Splitter contract + suite.reverse_splitter_init(&cfg); +} + +#[test] +#[should_panic(expected = "Configuration error: Invalid split config: amount cannot be zero.")] +fn instantiate_fails_for_zero_amount() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = suite.account_init("input_account", vec![]); + + // Configure reverse splitter with invalid split config + let split_cfg = UncheckedSplitConfig::with_native_amount(ZERO, NTRN, &input_addr); + let cfg = suite.reverse_splitter_config(vec![split_cfg], UncheckedDenom::Native(NTRN.into())); + + // Instantiate Reverse Splitter contract + suite.reverse_splitter_init(&cfg); +} + +#[test] +#[should_panic(expected = "Configuration error: Invalid split config: ratio cannot be zero.")] +fn instantiate_fails_for_zero_ratio() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = suite.account_init("input_account", vec![]); + + // Configure reverse splitter with invalid split config + let split_cfg = UncheckedSplitConfig::with_native_ratio(Decimal::zero(), NTRN, &input_addr); + let cfg = suite.reverse_splitter_config(vec![split_cfg], UncheckedDenom::Native(NTRN.into())); + + // Instantiate Reverse Splitter contract + suite.reverse_splitter_init(&cfg); +} + +#[test] +#[should_panic( + expected = "Configuration error: Duplicate split 'Native(\"untrn\")|AccountAddr(\"cosmwasm1xj6u4ccauyhvylgtj82x2qqc34lk3xuzw4mujevzqyr4gj7gqsjsv4856c\")' in split config." +)] +fn instantiate_fails_for_duplicate_split() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = suite.account_init("input_account", vec![]); + + // Configure splitter with duplicate split + let split_cfg = UncheckedSplitConfig::with_native_amount(ONE_MILLION, NTRN, &input_addr); + let cfg = suite.reverse_splitter_config( + vec![split_cfg.clone(), split_cfg], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + suite.reverse_splitter_init(&cfg); +} + +#[test] +#[should_panic(expected = "Configuration error: No split configuration provided.")] +fn update_config_validates_config() { + let mut suite = ReverseSplitterTestSuite::default(); + + // Initialize input account with 1_000_000 NTRN + let input_addr = + suite.account_init_with_balances("input_account", vec![(ONE_MILLION, NTRN.into())]); + + let mut cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_native_amount( + ONE_MILLION, + NTRN, + &input_addr, + )], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Update config to clear all split configs + cfg.splits.clear(); + + // Execute update config action + suite.update_config(svc.clone(), cfg).unwrap(); +} + +#[test] +fn update_config_with_valid_config() { + let mut suite = ReverseSplitterTestSuite::default(); + + // Initialize input account with 1_000_000 NTRN + let input_addr = + suite.account_init_with_balances("input_account", vec![(ONE_MILLION, NTRN.into())]); + + let mut cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_native_amount( + ONE_MILLION, + NTRN, + &input_addr, + )], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Update config to a split config for STARS based on ratio, + // and swap input and output addresses. + cfg.splits.push(UncheckedSplitConfig::with_native_ratio( + Decimal::percent(10u64), + STARS, + &suite.output_addr, + )); + cfg.output_addr = (&input_addr).into(); + + // Execute update config action + suite.update_config(svc.clone(), cfg).unwrap(); + + // Verify service config + let svc_cfg: Config = suite.query_wasm(&svc, &QueryMsg::GetServiceConfig {}); + assert_eq!( + svc_cfg, + Config::new( + input_addr.clone(), + vec![ + SplitConfig::new( + CheckedDenom::Native(NTRN.into()), + input_addr, + SplitAmount::FixedAmount(ONE_MILLION.into()), + None + ), + SplitConfig::new( + CheckedDenom::Native(STARS.into()), + suite.output_addr().clone(), + SplitAmount::FixedRatio(Decimal::percent(10u64)), + None + ) + ], + CheckedDenom::Native(NTRN.into()) + ) + ); +} + +// Native & CW20 token amount splits + +#[test] +fn split_native_single_token_amount_single_input() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = + suite.account_init_with_balances("input_account", vec![(ONE_MILLION, NTRN.into())]); + + let cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_native_amount( + ONE_MILLION, + NTRN, + &input_addr, + )], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input_addr, ZERO, NTRN); + + // Verify output account's balance: should be 1_000_000 NTRN + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); +} + +#[test] +fn split_native_single_token_amount_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = suite + .account_init_with_balances("input_account_1", vec![(600_000_000_000_u128, NTRN.into())]); + let input2_addr = suite + .account_init_with_balances("input_account_2", vec![(400_000_000_000_u128, NTRN.into())]); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_amount(600_000_000_000_u128, NTRN, &input1_addr), + UncheckedSplitConfig::with_native_amount(400_000_000_000_u128, NTRN, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input accounts balances: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance(&input2_addr, ZERO, NTRN); + + // Verify output account's balance: should be 1_000_000 NTRN + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); +} + +#[test] +fn split_native_two_token_amounts_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + let input2_addr = + suite.account_init_with_balances("input_account_2", vec![(TEN_MILLION, STARS.into())]); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_amount(ONE_MILLION, NTRN, &input1_addr), + UncheckedSplitConfig::with_native_amount(TEN_MILLION, STARS, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Assert initial balances + suite.assert_balance(&input1_addr, ONE_MILLION, NTRN); + suite.assert_balance(&input2_addr, TEN_MILLION, STARS); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance(&input2_addr, ZERO, STARS); + + // Verify output account's balances: should be 1_000_000 NTRN & 10_000_000 STARS + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); + suite.assert_balance(suite.output_addr(), TEN_MILLION, STARS); +} + +#[test] +fn split_cw20_single_token_amount_single_input() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input_addr = suite.account_init_with_balances("input_account", vec![]); + + let cw20_addr = + suite.cw20_token_init(MEME, "MEME", vec![(ONE_MILLION, input_addr.to_string())]); + + let cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_cw20_amount( + ONE_MILLION, + &cw20_addr, + &input_addr, + )], + UncheckedDenom::Cw20(cw20_addr.to_string()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_cw20_balance(&input_addr, ZERO, &cw20_addr); + + // Verify output account's balance: should be 1_000_000 MEME + suite.assert_cw20_balance(suite.output_addr(), ONE_MILLION, &cw20_addr); +} + +#[test] +fn split_cw20_single_token_amount_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = suite.account_init_with_balances("input_account_1", vec![]); + let input2_addr = suite.account_init_with_balances("input_account_2", vec![]); + + let cw20_addr = suite.cw20_token_init( + MEME, + "MEME", + vec![ + (600_000_000_000_u128, input1_addr.to_string()), + (400_000_000_000_u128, input2_addr.to_string()), + ], + ); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_cw20_amount(600_000_000_000_u128, &cw20_addr, &input1_addr), + UncheckedSplitConfig::with_cw20_amount(400_000_000_000_u128, &cw20_addr, &input2_addr), + ], + UncheckedDenom::Cw20(cw20_addr.to_string()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input accounts balances: should be zero + suite.assert_cw20_balance(&input1_addr, ZERO, &cw20_addr); + suite.assert_cw20_balance(&input2_addr, ZERO, &cw20_addr); + + // Verify output account's balance: should be 1_000_000 MEME + suite.assert_cw20_balance(suite.output_addr(), ONE_MILLION, &cw20_addr); +} + +#[test] +fn split_cw20_two_token_amounts_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = suite.account_init_with_balances("input_account_1", vec![]); + let input2_addr = suite.account_init_with_balances("input_account_2", vec![]); + + let cw20_1_addr = + suite.cw20_token_init(MEME, "MEME", vec![(ONE_MILLION, input1_addr.to_string())]); + let cw20_2_addr = + suite.cw20_token_init(CATZ, "CATZ", vec![(TEN_MILLION, input2_addr.to_string())]); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_cw20_amount(ONE_MILLION, &cw20_1_addr, &input1_addr), + UncheckedSplitConfig::with_cw20_amount(TEN_MILLION, &cw20_2_addr, &input2_addr), + ], + UncheckedDenom::Cw20(cw20_1_addr.to_string()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Assert initial balances + suite.assert_cw20_balance(&input1_addr, ONE_MILLION, &cw20_1_addr); + suite.assert_cw20_balance(&input2_addr, TEN_MILLION, &cw20_2_addr); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input accounts balances: should be zero + suite.assert_cw20_balance(&input1_addr, ZERO, &cw20_1_addr); + suite.assert_cw20_balance(&input2_addr, ZERO, &cw20_2_addr); + + // Verify output account's balances: should be 1_000_000 MEME & 10_000_000 CATZ + suite.assert_cw20_balance(suite.output_addr(), ONE_MILLION, &cw20_1_addr); + suite.assert_cw20_balance(suite.output_addr(), TEN_MILLION, &cw20_2_addr); +} + +#[test] +fn split_mix_two_token_amounts_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + let input2_addr = suite.account_init_with_balances("input_account_2", vec![]); + + let cw20_addr = + suite.cw20_token_init(CATZ, "CATZ", vec![(TEN_MILLION, input2_addr.to_string())]); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_amount(ONE_MILLION, NTRN, &input1_addr), + UncheckedSplitConfig::with_cw20_amount(TEN_MILLION, &cw20_addr, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Assert initial balances + suite.assert_balance(&input1_addr, ONE_MILLION, NTRN); + suite.assert_cw20_balance(&input2_addr, TEN_MILLION, &cw20_addr); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_cw20_balance(&input1_addr, ZERO, &cw20_addr); + + // Verify output account's balances: should be 1_000_000 MEME & 10_000_000 CATZ + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); + suite.assert_cw20_balance(suite.output_addr(), TEN_MILLION, &cw20_addr); +} + +#[test] +fn split_native_two_token_partial_amounts_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = suite.account_init_with_balances( + "input_account_1", + vec![(FIVE_MILLION, NTRN.into()), (HALF_MILLION, STARS.into())], + ); + let input2_addr = suite.account_init_with_balances( + "input_account_2", + vec![(FIVE_MILLION, NTRN.into()), (HALF_MILLION, STARS.into())], + ); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_amount(FIVE_MILLION, NTRN, &input1_addr), + UncheckedSplitConfig::with_native_amount(HALF_MILLION, STARS, &input1_addr), + UncheckedSplitConfig::with_native_amount(FIVE_MILLION, NTRN, &input2_addr), + UncheckedSplitConfig::with_native_amount(HALF_MILLION, STARS, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Assert initial balances + suite.assert_balance(&input1_addr, FIVE_MILLION, NTRN); + suite.assert_balance(&input1_addr, HALF_MILLION, STARS); + suite.assert_balance(&input2_addr, FIVE_MILLION, NTRN); + suite.assert_balance(&input2_addr, HALF_MILLION, STARS); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance(&input1_addr, ZERO, STARS); + suite.assert_balance(&input2_addr, ZERO, NTRN); + suite.assert_balance(&input2_addr, ZERO, STARS); + + // Verify output account 1's balance: should be 5 million STARS & half a million NTRN + suite.assert_balance(suite.output_addr(), TEN_MILLION, NTRN); + suite.assert_balance(suite.output_addr(), ONE_MILLION, STARS); +} + +// Insufficient balance tests + +#[test] +#[should_panic( + expected = "Execution error: Insufficient balance on account cosmwasm18ygxc482fgklywq5e2fsnmnkqflwaq5u07f9yw824ajfu2x6920sv28wwu for denom 'Native(\"untrn\")' in split config (required: 10000000000000, available: 1000000000000)." +)] +fn split_native_single_token_amount_fails_for_insufficient_balance() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + + let cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_native_amount( + TEN_MILLION, + NTRN, + &input1_addr, + )], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); +} + +#[test] +#[should_panic( + expected = "Execution error: Insufficient balance on account cosmwasm18ygxc482fgklywq5e2fsnmnkqflwaq5u07f9yw824ajfu2x6920sv28wwu for denom 'Cw20(Addr(\"cosmwasm1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqlrtkzd\"))' in split config (required: 10000000000000, available: 1000000000000)." +)] +fn split_cw20_single_token_amount_fails_for_insufficient_balance() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = suite.account_init_with_balances("input_account_1", vec![]); + + let cw20_addr = + suite.cw20_token_init(MEME, "MEME", vec![(ONE_MILLION, input1_addr.to_string())]); + + let cfg = suite.reverse_splitter_config( + vec![UncheckedSplitConfig::with_cw20_amount( + TEN_MILLION, + &cw20_addr, + &input1_addr, + )], + UncheckedDenom::Cw20(cw20_addr.to_string()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); +} + +// Native & CW20 token ratio splits + +#[test] +fn split_native_two_token_ratios_two_inputs() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + let input2_addr = + suite.account_init_with_balances("input_account_2", vec![(TEN_MILLION, STARS.into())]); + + // Hypothetical ratio for NTRN/STARS is 1:10 + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_ratio(Decimal::one(), NTRN, &input1_addr), + UncheckedSplitConfig::with_native_ratio(Decimal::percent(10u64), STARS, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input accounts balances: should be zero for both + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance(&input2_addr, ZERO, STARS); + + // Verify output account's balances: should be 1_000_000 NTRN & 10_000_000 STARS + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); + suite.assert_balance(suite.output_addr(), TEN_MILLION, STARS); +} + +#[test] +fn split_mix_three_token_ratios_three_inputs() { + const NTRN_AMOUNT: u128 = ONE_MILLION / 3; + const STARS_AMOUNT: u128 = TEN_MILLION; + const MEME_AMOUNT: u128 = HUNDRED_MILLION / 2; + const NTRN_STARS_RATIO: u128 = 10; + const NTRN_MEME_RATIO: u128 = 100; + + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(NTRN_AMOUNT, NTRN.into())]); + let input2_addr = + suite.account_init_with_balances("input_account_2", vec![(STARS_AMOUNT, STARS.into())]); + let input3_addr = suite.account_init_with_balances("input_account_3", vec![]); + + let cw20_addr = + suite.cw20_token_init(MEME, "MEME", vec![(MEME_AMOUNT, input3_addr.to_string())]); + + // Hypothetical ratios: + // NTRN/STARS is 1:10 + // NTRN/MEME is 1:100 + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_ratio(Decimal::one(), NTRN, &input1_addr), + UncheckedSplitConfig::with_native_ratio( + Decimal::percent(100u64 / NTRN_STARS_RATIO as u64), + STARS, + &input2_addr, + ), + UncheckedSplitConfig::with_cw20_ratio( + Decimal::percent(100u64 / NTRN_MEME_RATIO as u64), + &cw20_addr, + &input3_addr, + ), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // The expected splits are based on the fact that the Reverse Splitter + // will transfer as big an amount as possible in the base denom. + // + // Starting balances in input account: + // NTRN: 333_333 + // STARS: 10_000_000 + // MEME: 50_000_000 + // + // Expected transferred amounts: + // NTRN: 333_333 + // STARS: 333_333 * 10 = 3_333_330 + // MEME: 333_333 * 100 = 33_333_300 + // + // Expected remaining balances: + // NTRN: 333_333 - 333_333 = 0 + // STARS: 10_000_000 - 3_333_330 = 6_666_670 + // MEME: 50_000_000 - 33_333_300 = 16_666_700 + + // Verify input account balances + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance( + &input2_addr, + STARS_AMOUNT - (NTRN_AMOUNT * NTRN_STARS_RATIO), + STARS, + ); + suite.assert_cw20_balance( + &input3_addr, + MEME_AMOUNT - (NTRN_AMOUNT * NTRN_MEME_RATIO), + &cw20_addr, + ); + + // Verify output account's balances: should be 333_333 NTRN, 3_333_330 STARS, 33_333_300 MEME + suite.assert_balance(suite.output_addr(), NTRN_AMOUNT, NTRN); + suite.assert_balance(suite.output_addr(), NTRN_AMOUNT * NTRN_STARS_RATIO, STARS); + suite.assert_cw20_balance( + suite.output_addr(), + NTRN_AMOUNT * NTRN_MEME_RATIO, + &cw20_addr, + ); +} + +// Dynamic ratio tests + +#[test] +fn split_native_single_token_dyn_ratio_single_input() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + let input2_addr = + suite.account_init_with_balances("input_account_2", vec![(TEN_MILLION, STARS.into())]); + + let dyn_ratio_addr = suite.dyn_ratio_contract_init(STARS, Decimal::percent(10u64)); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_ratio(Decimal::one(), NTRN, &input1_addr), + UncheckedSplitConfig::with_native_dyn_ratio(&dyn_ratio_addr, "", STARS, &input2_addr), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_balance(&input2_addr, ZERO, STARS); + + // Verify output account's balance: should be 1_000_000 NTRN + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); + suite.assert_balance(suite.output_addr(), TEN_MILLION, STARS); +} + +#[test] +fn split_cw20_single_token_dyn_ratio_single_output() { + let mut suite = ReverseSplitterTestSuite::default(); + + let input1_addr = + suite.account_init_with_balances("input_account_1", vec![(ONE_MILLION, NTRN.into())]); + let input2_addr = suite.account_init_with_balances("input_account_2", vec![]); + + let cw20_addr = + suite.cw20_token_init(MEME, "MEME", vec![(TEN_MILLION, input2_addr.to_string())]); + + let dyn_ratio_addr = suite.dyn_ratio_contract_init(cw20_addr.as_ref(), Decimal::percent(10u64)); + + let cfg = suite.reverse_splitter_config( + vec![ + UncheckedSplitConfig::with_native_ratio(Decimal::one(), NTRN, &input1_addr), + UncheckedSplitConfig::with_cw20_dyn_ratio( + &dyn_ratio_addr, + "", + &cw20_addr, + &input2_addr, + ), + ], + UncheckedDenom::Native(NTRN.into()), + ); + + // Instantiate Reverse Splitter contract + let svc = suite.reverse_splitter_init(&cfg); + + // Execute split + suite.execute_split(svc).unwrap(); + + // Verify input account's balance: should be zero + suite.assert_balance(&input1_addr, ZERO, NTRN); + suite.assert_cw20_balance(&input2_addr, ZERO, &cw20_addr); + + // Verify output account's balance: should be 1_000_000 MEME + suite.assert_balance(suite.output_addr(), ONE_MILLION, NTRN); + suite.assert_cw20_balance(suite.output_addr(), TEN_MILLION, &cw20_addr); +} diff --git a/contracts/testing/test-dynamic-ratio/Cargo.toml b/contracts/testing/test-dynamic-ratio/Cargo.toml index 9262f3f..f1582c5 100644 --- a/contracts/testing/test-dynamic-ratio/Cargo.toml +++ b/contracts/testing/test-dynamic-ratio/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" authors = ["Timewave Labs"] edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] crate-type = ["cdylib", "rlib"] @@ -30,4 +28,7 @@ valence-service-base = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } cw-ownable = { workspace = true } +cw20 = { workspace = true } +cw20-base = { workspace = true } +sha2 = { workspace = true } valence-service-utils = { workspace = true, features = ["testing"] } diff --git a/packages/service-utils/src/testing.rs b/packages/service-utils/src/testing.rs index f366e74..fb44a08 100644 --- a/packages/service-utils/src/testing.rs +++ b/packages/service-utils/src/testing.rs @@ -76,6 +76,30 @@ pub trait ServiceTestSuite { self.contract_init2(self.account_code_id(), salt, &init_msg, &[]) } + fn account_init_with_balances(&mut self, salt: &str, balances: Vec<(u128, String)>) -> Addr { + let addr = self.account_init(salt, vec![]); + + if !balances.is_empty() { + let balances = balances + .into_iter() + .map(|(amount, denom)| coin(amount, denom)) + .collect(); + self.init_balance(&addr, balances); + } + + addr + } + + fn account_approve_service(&mut self, addr: Addr, service: String) -> AnyResult { + let sender = self.owner().clone(); + self.app_mut().execute_contract( + sender, + addr, + &valence_account_utils::msg::ExecuteMsg::ApproveService { service }, + &[], + ) + } + fn get_contract_addr(&mut self, code_id: u64, salt: &str) -> Addr { let mut hasher = Sha256::new(); hasher.update(salt); diff --git a/workflow-manager/Cargo.toml b/workflow-manager/Cargo.toml index a4a30ea..c405f78 100644 --- a/workflow-manager/Cargo.toml +++ b/workflow-manager/Cargo.toml @@ -22,12 +22,12 @@ valence-processor-utils = { workspace = true } valence-workflow-registry-utils = { workspace = true } valence-service-base = { workspace = true } -valence-splitter-service = { workspace = true } -valence-reverse-splitter = { workspace = true } -valence-account-utils = { workspace = true } -valence-authorization = { workspace = true } -valence-processor = { workspace = true } -valence-workflow-registry = { workspace = true } +valence-splitter-service = { workspace = true } +valence-reverse-splitter-service = { workspace = true } +valence-account-utils = { workspace = true } +valence-authorization = { workspace = true } +valence-processor = { workspace = true } +valence-workflow-registry = { workspace = true } aho-corasick = "1.1" serde_json_any_key = "2" diff --git a/workflow-manager/src/service.rs b/workflow-manager/src/service.rs index 87f856c..0138e21 100644 --- a/workflow-manager/src/service.rs +++ b/workflow-manager/src/service.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use serde_json::to_vec; use strum::VariantNames; use thiserror::Error; -use valence_reverse_splitter::msg::ServiceConfig as ReverseSplitterServiceConfig; +use valence_reverse_splitter_service::msg::ServiceConfig as ReverseSplitterServiceConfig; use valence_service_utils::{msg::InstantiateMsg, Id, ServiceConfigInterface}; use valence_splitter_service::msg::ServiceConfig as SplitterServiceConfig;