diff --git a/.cargo/config.toml b/.cargo/config.toml index 8e019be0..c333e552 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,5 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" unit-test= "test --lib" +format = "fmt --all" +lint = "clippy -- -W clippy::pedantic" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2275fc0a..3de47d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,46 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abstract-cw-multi-test" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c77f8d4bac08f74fbc4fce8943cb2d35e742682b6cae8cb65555d6cd3830feb" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw20-ics20", + "derivative", + "hex", + "itertools 0.12.1", + "log", + "prost 0.12.6", + "schemars", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.7.8" @@ -24,15 +64,158 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "astroport" +version = "5.7.0" +source = "git+https://github.com/astroport-fi/astroport-core?branch=main#54d65dcbe9cf652c024fc0110b531f190efafce6" +dependencies = [ + "astroport-circular-buffer", + "cosmos-sdk-proto 0.19.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus", + "cw-utils", + "cw20 1.1.2", + "itertools 0.12.1", + "prost 0.11.9", + "uint", +] + +[[package]] +name = "astroport-circular-buffer" +version = "0.2.0" +source = "git+https://github.com/astroport-fi/astroport-core?branch=main#54d65dcbe9cf652c024fc0110b531f190efafce6" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "thiserror", +] + +[[package]] +name = "astroport-forwarding" +version = "0.2.1" +dependencies = [ + "astroport", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-orch", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw20 1.1.2", + "euclid", + "forwarding", + "mock", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "async-trait" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] [[package]] name = "base16ct" @@ -40,6 +223,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -64,6 +253,34 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac", + "k256", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.8", + "subtle", + "zeroize", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "block-buffer" version = "0.9.0" @@ -88,6 +305,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2 0.10.8", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -96,9 +322,21 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -112,14 +350,91 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cosmos-sdk-proto" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +dependencies = [ + "prost 0.11.9", + "prost-types 0.11.9", + "tendermint-proto 0.32.2", +] + +[[package]] +name = "cosmos-sdk-proto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" +dependencies = [ + "prost 0.12.6", + "prost-types 0.12.6", + "tendermint-proto 0.34.1", +] + +[[package]] +name = "cosmos-sdk-proto" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" +dependencies = [ + "prost 0.12.6", + "prost-types 0.12.6", + "tendermint-proto 0.35.0", +] + +[[package]] +name = "cosmrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" +dependencies = [ + "bip32", + "cosmos-sdk-proto 0.20.0", + "ecdsa", + "eyre", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "signature", + "subtle-encoding", + "tendermint 0.34.1", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" -version = "1.5.5" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" dependencies = [ "digest 0.10.7", - "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -128,18 +443,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.5" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" +checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.3" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e3a2136e2a60e8b6582f5dffca5d1a683ed77bf38537d330bc1dfccd69010" +checksum = "93d388adfa9cb449557a92e9318121ac1a481fc4f599213b03a5b62699b403b4" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -150,9 +465,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.3" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" +checksum = "2411b389e56e6484f81ba955b758d02522d620c98fc960c4bd2251d48b7aa19f" dependencies = [ "proc-macro2", "quote", @@ -161,11 +476,11 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.5" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" +checksum = "c21fde95ccd20044a23c0ac6fd8c941f3e8c158169dc94b5aa6491a2d9551a8d" dependencies = [ - "base64", + "base64 0.21.7", "bech32 0.9.1", "bnum", "cosmwasm-crypto", @@ -183,13 +498,19 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -226,86 +547,330 @@ dependencies = [ ] [[package]] -name = "cw-multi-test" -version = "1.2.0" +name = "curve25519-dalek-ng" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc33b1d65c102d72f46548c64dca423c337e528d6747d0c595316aa65f887b" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" dependencies = [ - "anyhow", - "bech32 0.11.0", "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "derivative", - "itertools 0.13.0", - "prost", - "schemars", - "serde", - "sha2 0.10.8", - "thiserror", ] [[package]] -name = "cw-storage-plus" -version = "1.2.0" +name = "cw-asset" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +checksum = "c999a12f8cd8736f6f86e9a4ede5905530cb23cfdef946b9da1c506ad1b70799" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", - "schemars", - "serde", + "cw-address-like", + "cw-storage-plus", + "cw20 1.1.2", + "thiserror", ] [[package]] -name = "cw-utils" -version = "1.0.3" +name = "cw-controllers" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" +checksum = "57de8d3761e46be863e3ac1eba8c8a976362a48c6abf240df1e26c3e421ee9e8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2", + "cw-storage-plus", + "cw-utils", "schemars", - "semver", "serde", "thiserror", ] [[package]] -name = "cw2" -version = "1.1.2" +name = "cw-multi-test" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +checksum = "91fc33b1d65c102d72f46548c64dca423c337e528d6747d0c595316aa65f887b" dependencies = [ - "cosmwasm-schema", + "anyhow", + "bech32 0.11.0", "cosmwasm-std", "cw-storage-plus", + "cw-utils", + "derivative", + "itertools 0.13.0", + "prost 0.12.6", "schemars", - "semver", "serde", + "sha2 0.10.8", "thiserror", ] [[package]] -name = "cw20" -version = "0.1.0" +name = "cw-multicall" +version = "0.1.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus", "cw2", - "cw20 1.1.2", - "cw20-base", "euclid", + "euclid-utils", + "mock", "schemars", "serde", - "thiserror", ] [[package]] -name = "cw20" -version = "1.1.2" +name = "cw-orch" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c81cb500eb2f9be31a0f90c7ce66572ee4a790ffbae1c6b42ff2e3f9faf3479" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-orch-contract-derive", + "cw-orch-core", + "cw-orch-fns-derive", + "cw-orch-mock", + "cw-orch-traits", + "cw-utils", + "hex", + "log", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-orch-contract-derive" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad52865e313bb7ed3f3938f7ad9d566e430fb6143a63476c22bed505ea78cd7" +dependencies = [ + "convert_case", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "cw-orch-core" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9466093ad8bf067f9eebbe25835ada3ea155726ca557d9d1c7681538078ef24f" +dependencies = [ + "abstract-cw-multi-test", + "anyhow", + "cosmos-sdk-proto 0.21.1", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "dirs", + "log", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-orch-fns-derive" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e21b23116a0702f540d7fa3f16e8276682d860b589fed56259220ad59d768e" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cw-orch-interchain" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba25ad0bf28edb98a8eb00a4bb1a922f5e9555b52512c4017cae36a676d671ed" +dependencies = [ + "cosmwasm-std", + "cw-orch-interchain-core", + "cw-orch-interchain-mock", + "cw1", + "cw1-whitelist", + "ibc-relayer-types", + "speculoos", +] + +[[package]] +name = "cw-orch-interchain-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbe966c1c30f655f704ab201b15219e4e5c01592465bbda8b39fb015e79873b2" +dependencies = [ + "base64 0.21.7", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch-core", + "cw-orch-mock", + "futures", + "ibc-relayer-types", + "log", + "polytone", + "prost 0.12.6", + "serde_json", + "thiserror", + "tokio", + "tonic 0.10.2", +] + +[[package]] +name = "cw-orch-interchain-mock" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e03b82fb8ae2dd93f04fce878edeb688ba9ceaa7efc27d35f4213a8eadecfa" +dependencies = [ + "anyhow", + "cosmrs", + "cosmwasm-std", + "cw-orch-core", + "cw-orch-interchain-core", + "cw-orch-mock", + "cw-utils", + "ibc-relayer-types", + "log", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cw-orch-mock" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57beb30d841bde79df51c9402741ef926ca8ef7ecd3570aa180074f767ac04d3" +dependencies = [ + "abstract-cw-multi-test", + "cosmwasm-std", + "cw-orch-core", + "cw-utils", + "log", + "serde", + "sha2 0.10.8", +] + +[[package]] +name = "cw-orch-traits" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e6b81dc282724c9c6334a499f4867e575458e69fe5b99034d4f962860f3357" +dependencies = [ + "cw-orch-core", + "prost 0.12.6", + "prost-types 0.12.6", +] + +[[package]] +name = "cw-storage-plus" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw1" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1605722190afd93bfea6384b88224d1cfe50ebf70d2e10641535da79fa70e83" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw1-whitelist" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bb3e9dc87f4ff26547f4e27e0ba3c82034372f21b2f55527fb52b542637d8d" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw1", + "cw2", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "0.2.1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-orch", + "cw-storage-plus", + "cw2", + "cw20 1.1.2", + "cw20-base", + "euclid", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" dependencies = [ @@ -333,16 +898,44 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw20-ics20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76221201da08fed611c857ea3aa21c031a4a7dc771a8b1750559ca987335dc02" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw20 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -354,6 +947,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "digest" version = "0.9.0" @@ -375,6 +979,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -395,6 +1020,29 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "3.1.0" @@ -402,7 +1050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ "curve25519-dalek", - "hashbrown", + "hashbrown 0.12.3", "hex", "rand_core 0.6.4", "serde", @@ -412,9 +1060,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -435,13 +1083,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + [[package]] name = "escrow" -version = "0.1.0" +version = "0.2.2" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus", "cw2", "cw20 1.1.2", @@ -454,11 +1118,12 @@ dependencies = [ [[package]] name = "euclid" -version = "0.1.0" +version = "0.2.1" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-orch", "cw-storage-plus", "cw-utils", "cw20 1.1.2", @@ -474,7 +1139,7 @@ dependencies = [ [[package]] name = "euclid-ibc" -version = "0.1.0" +version = "0.2.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -486,12 +1151,35 @@ dependencies = [ ] [[package]] -name = "factory" +name = "euclid-utils" version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "euclid", + "schemars", + "serde", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "factory" +version = "0.2.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus", "cw-utils", "cw2", @@ -516,16 +1204,57 @@ dependencies = [ ] [[package]] -name = "forward_ref" -version = "1.0.0" +name = "fixed-hash" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "anyhow", + "eyre", + "paste", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "forwarding" +version = "0.1.0" +dependencies = [ + "astroport", + "cosmwasm-schema", + "cosmwasm-std", + "cw-orch", + "cw-storage-plus", + "cw20 1.1.2", + "euclid", + "schemars", + "serde", +] [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -538,9 +1267,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -548,15 +1277,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -565,32 +1294,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.79", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -600,9 +1329,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -629,15 +1358,21 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "glob" version = "0.3.1" @@ -655,6 +1390,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -664,6 +1418,18 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -679,6 +1445,184 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "ibc-proto" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c352715b36685c2543556a77091fb16af5d26257d5ce9c28e6756c1ccd71aa" +dependencies = [ + "base64 0.21.7", + "bytes", + "flex-error", + "ics23", + "prost 0.11.9", + "serde", + "subtle-encoding", + "tendermint-proto 0.32.2", + "tonic 0.9.2", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa9269c050d20b36a9e61955a5526345df1508f396f7f3a9acb4c03cdb572f3" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto", + "ics23", + "itertools 0.10.5", + "num-rational", + "primitive-types", + "prost 0.11.9", + "regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.32.2", + "tendermint-light-client-verifier", + "tendermint-proto 0.32.2", + "time", + "uint", +] + +[[package]] +name = "ics23" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442d4bab37956e76f739c864f246c825d87c0bb7f9afa65660c57833c91bf6d4" +dependencies = [ + "anyhow", + "bytes", + "hex", + "informalsystems-pbjson", + "prost 0.11.9", + "ripemd", + "serde", + "sha2 0.10.8", + "sha3", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", +] + +[[package]] +name = "informalsystems-pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eecd90f87bea412eac91c6ef94f6b1e390128290898cbe14f2b926787ae1fb" +dependencies = [ + "base64 0.13.1", + "serde", +] + [[package]] name = "itertools" version = "0.10.5" @@ -690,9 +1634,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -714,9 +1658,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -726,11 +1670,42 @@ dependencies = [ "signature", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" -version = "0.2.153" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" @@ -738,6 +1713,33 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "mock" version = "0.1.0" @@ -756,147 +1758,437 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "num" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "num-complex" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] [[package]] -name = "pin-utils" +name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "pkcs8" -version = "0.10.2" +name = "num-derive" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "der", - "spki", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "proc-macro2" -version = "1.0.79" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", + "syn 2.0.79", ] [[package]] -name = "prost" -version = "0.12.6" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "bytes", - "prost-derive", + "num-traits", ] [[package]] -name = "prost-derive" -version = "0.12.6" +name = "num-iter" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "anyhow", - "itertools 0.11.0", - "proc-macro2", - "quote", - "syn 2.0.55", + "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "quote" -version = "1.0.35" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "proc-macro2", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - -[[package]] -name = "rand_core" -version = "0.6.4" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "getrandom", + "autocfg", ] [[package]] -name = "regex" -version = "1.10.5" +name = "object" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ - "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", ] [[package]] -name = "regex-automata" -version = "0.4.7" +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] -name = "regex-syntax" -version = "0.8.4" +name = "opaque-debug" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "relative-path" -version = "1.9.3" +name = "openssl-probe" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] -name = "rfc6979" -version = "0.4.0" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polytone" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f16d20da9144fdf0658e785fc9108b86cecee517335ff531745029dd56088" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "thiserror", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", "subtle", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "router" -version = "0.1.0" +version = "0.2.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus", "cw-utils", "cw2", @@ -934,247 +2226,787 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.55", + "syn 2.0.79", "unicode-ident", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] -name = "ryu" +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.79", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "speculoos" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65881c9270d6157f30a09233305da51bed97eef9192d0ea21e57b1c8f05c3620" +dependencies = [ + "num", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tendermint" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.9", + "prost-types 0.11.9", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.32.2", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15ab8f0a25d0d2ad49ac615da054d6a76aa6603ff95f7d18bafdd34450a1a04b" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.12.6", + "prost-types 0.12.6", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.34.1", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9875dce5c1b08201152eb0860f8fb1dce96c53e37532c310ffc4956d20f90def" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.32.2", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.3.3", + "num-traits", + "prost 0.11.9", + "prost-types 0.11.9", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.3.3", + "num-traits", + "prost 0.12.6", + "prost-types 0.12.6", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" +dependencies = [ + "bytes", + "flex-error", + "num-derive 0.4.2", + "num-traits", + "prost 0.12.6", + "prost-types 0.12.6", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tests-integration" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-orch", + "cw-orch-interchain", + "cw-storage-plus", + "cw-utils", + "cw20 0.2.1", + "escrow", + "euclid", + "factory", + "ibc-relayer-types", + "itertools 0.10.5", + "mock", + "router", + "rstest", + "virtual_balance", + "vlp", +] [[package]] -name = "schemars" -version = "0.8.21" +name = "thiserror" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", + "thiserror-impl", ] [[package]] -name = "schemars_derive" -version = "0.8.21" +name = "thiserror-impl" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals", - "syn 2.0.55", + "syn 2.0.79", ] [[package]] -name = "sec1" -version = "0.7.3" +name = "time" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", ] [[package]] -name = "semver" -version = "1.0.22" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] -name = "serde" -version = "1.0.208" +name = "time-macros" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ - "serde_derive", + "num-conv", + "time-core", ] [[package]] -name = "serde-json-wasm" -version = "0.5.2" +name = "tokio" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "serde", + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", ] [[package]] -name = "serde_derive" -version = "1.0.208" +name = "tokio-io-timeout" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", + "pin-project-lite", + "tokio", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.79", ] [[package]] -name = "serde_json" -version = "1.0.115" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "itoa", - "ryu", - "serde", + "rustls", + "tokio", ] [[package]] -name = "sha2" -version = "0.9.9" +name = "tokio-stream" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "tokio-util" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "signature" -version = "2.2.0" +name = "tonic" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.9" + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ - "autocfg", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.12.6", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "base64ct", - "der", + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] -name = "subtle" -version = "2.5.0" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "syn" -version = "1.0.109" +name = "tracing" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "pin-project-lite", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "syn" -version = "2.0.55" +name = "tracing-attributes" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "unicode-ident", -] - -[[package]] -name = "tests-integration" -version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "escrow", - "euclid", - "factory", - "itertools 0.10.5", - "mock", - "router", - "rstest", - "virtual_balance", - "vlp", + "syn 2.0.79", ] [[package]] -name = "thiserror" -version = "1.0.61" +name = "tracing-core" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "thiserror-impl", + "once_cell", ] [[package]] -name = "thiserror-impl" -version = "1.0.61" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -1182,25 +3014,50 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "virtual_balance" -version = "0.1.0" +version = "0.2.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus", "cw2", "euclid", @@ -1212,11 +3069,12 @@ dependencies = [ [[package]] name = "vlp" -version = "0.1.0" +version = "0.2.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", + "cw-orch", "cw-storage-plus", "cw-utils", "cw2", @@ -1227,14 +3085,206 @@ dependencies = [ "thiserror", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] diff --git a/Cargo.toml b/Cargo.toml index 8221696c..d06d94fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ resolver = "2" members = [ "contracts/hub/*", "contracts/liquidity/*", + "contracts/forwarding/*", + "contracts/common/*", "packages/*", "tests-integration", ] @@ -23,6 +25,8 @@ strip = true [workspace.dependencies] euclid = { path = "./packages/euclid" } euclid-ibc = { path = "./packages/euclid_ibc" } +euclid-utils = { path = "./packages/euclid_utils" } +forwarding = { path = "./packages/forwarding" } cosmwasm-std = "1.5.3" cosmwasm-schema = "1.5.0" @@ -37,4 +41,7 @@ thiserror = { version = "1.0.49" } itertools = "0.10" mock = { path = "./packages/mock" } -cw-multi-test = { version = "1.2.0", features = ["cosmwasm_1_2"] } +cw-multi-test = { version = "1.2.0", features = [ + "cosmwasm_1_2", + "cosmwasm_1_1", +] } diff --git a/contracts/common/cw-multicall/.cargo/config.toml b/contracts/common/cw-multicall/.cargo/config.toml new file mode 100644 index 00000000..af5698e5 --- /dev/null +++ b/contracts/common/cw-multicall/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/common/cw-multicall/.circleci/config.yml b/contracts/common/cw-multicall/.circleci/config.yml new file mode 100644 index 00000000..f1368e54 --- /dev/null +++ b/contracts/common/cw-multicall/.circleci/config.yml @@ -0,0 +1,61 @@ +version: 2.1 + +executors: + builder: + docker: + - image: buildpack-deps:trusty + +jobs: + docker-image: + executor: builder + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: + name: Build Docker artifact + command: docker build --pull -t "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" . + - run: + name: Push application Docker image to docker hub + command: | + if [ "${CIRCLE_BRANCH}" = "master" ]; then + docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" cosmwasm/cw-gitpod-base:latest + docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" + docker push cosmwasm/cw-gitpod-base:latest + docker logout + fi + + docker-tagged: + executor: builder + steps: + - checkout + - setup_remote_docker: + docker_layer_caching: true + - run: + name: Push application Docker image to docker hub + command: | + docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" "cosmwasm/cw-gitpod-base:${CIRCLE_TAG}" + docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" + docker push + docker logout + +workflows: + version: 2 + test-suite: + jobs: + # this is now a slow process... let's only run on master + - docker-image: + filters: + branches: + only: + - master + - docker-tagged: + filters: + tags: + only: + - /^v.*/ + branches: + ignore: + - /.*/ + requires: + - docker-image diff --git a/contracts/common/cw-multicall/.editorconfig b/contracts/common/cw-multicall/.editorconfig new file mode 100644 index 00000000..3d36f20b --- /dev/null +++ b/contracts/common/cw-multicall/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/contracts/common/cw-multicall/.github/workflows/Basic.yml b/contracts/common/cw-multicall/.github/workflows/Basic.yml new file mode 100644 index 00000000..3890a07c --- /dev/null +++ b/contracts/common/cw-multicall/.github/workflows/Basic.yml @@ -0,0 +1,75 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: [push, pull_request] + +name: Basic + +jobs: + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + target: wasm32-unknown-unknown + override: true + + - name: Run unit tests + uses: actions-rs/cargo@v1 + with: + command: unit-test + args: --locked + env: + RUST_BACKTRACE: 1 + + - name: Compile WASM contract + uses: actions-rs/cargo@v1 + with: + command: wasm + args: --locked + env: + RUSTFLAGS: "-C link-arg=-s" + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings + + - name: Generate Schema + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked + + - name: Schema Changes + # fails if any changes not committed + run: git diff --exit-code schema diff --git a/contracts/common/cw-multicall/.github/workflows/Release.yml b/contracts/common/cw-multicall/.github/workflows/Release.yml new file mode 100644 index 00000000..a9d59794 --- /dev/null +++ b/contracts/common/cw-multicall/.github/workflows/Release.yml @@ -0,0 +1,35 @@ +name: release wasm + +on: + release: + types: [created] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install cargo-run-script + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-run-script + - name: Run cargo optimize + uses: actions-rs/cargo@v1 + with: + command: run-script + args: optimize + - name: Get release ID + id: get_release + uses: bruceadams/get-release@v1.2.3 + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload optimized wasm + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ./artifacts/*.wasm + tag: ${{ github.ref }} + overwrite: true + file_glob: true diff --git a/contracts/common/cw-multicall/.gitignore b/contracts/common/cw-multicall/.gitignore new file mode 100644 index 00000000..9095deaa --- /dev/null +++ b/contracts/common/cw-multicall/.gitignore @@ -0,0 +1,16 @@ +# Build results +/target +/schema + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/common/cw-multicall/Cargo.toml b/contracts/common/cw-multicall/Cargo.toml new file mode 100644 index 00000000..454a4b85 --- /dev/null +++ b/contracts/common/cw-multicall/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "cw-multicall" +version = "0.1.1" +authors = ["Anshudhar Kumar Singh "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# 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.15.0 +""" + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true, features = [ + "cosmwasm_1_2", + # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher + # "cosmwasm_1_4", +] } +cw-storage-plus = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, default-features = false, features = ["derive"] } +euclid = { workspace = true } +euclid-utils = { workspace = true } +cw2 = { workspace = true } + + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +cw-multi-test = { workspace = true } +mock = { workspace = true } diff --git a/contracts/common/cw-multicall/LICENSE b/contracts/common/cw-multicall/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/contracts/common/cw-multicall/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/common/cw-multicall/NOTICE b/contracts/common/cw-multicall/NOTICE new file mode 100644 index 00000000..e7d57816 --- /dev/null +++ b/contracts/common/cw-multicall/NOTICE @@ -0,0 +1,13 @@ +Copyright 2024 gachouchani1999 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/common/cw-multicall/README.md b/contracts/common/cw-multicall/README.md new file mode 100644 index 00000000..b4b01a7e --- /dev/null +++ b/contracts/common/cw-multicall/README.md @@ -0,0 +1,4 @@ +# Escrow Contract +The Escrow smart contract is a simple contract that holds one type of token. Each integrated chain will have these escrows deployed, holding the liquidity for the tokens. Refer to the following resources to learn more about the Escrow Contract: +- [Architecture](https://docs.euclidprotocol.io/docs/Architecture%20Overview/Architecture/Integrated%20Chains%20Layer/escrows) +- [Detailed Breakdown of the Escrow's available messages](https://docs.euclidprotocol.io/docs/Euclid%20Smart%20Contracts/CosmWasm/Escrow) \ No newline at end of file diff --git a/contracts/common/cw-multicall/src/bin/schema.rs b/contracts/common/cw-multicall/src/bin/schema.rs new file mode 100644 index 00000000..d3cf12c8 --- /dev/null +++ b/contracts/common/cw-multicall/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use euclid_utils::msgs::multicall::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/common/cw-multicall/src/contract.rs b/contracts/common/cw-multicall/src/contract.rs new file mode 100644 index 00000000..6c3b7c6f --- /dev/null +++ b/contracts/common/cw-multicall/src/contract.rs @@ -0,0 +1,56 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError}; + +use euclid::error::ContractError; + +use crate::query::query_multi_queries; +use cw2::set_contract_version; + +use euclid_utils::msgs::multicall::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw-multicall"; +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)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("contract", CONTRACT_NAME) + .add_attribute("version", CONTRACT_VERSION)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg {} +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { + match msg { + // New escrow queries + QueryMsg::MultiQuery { queries } => query_multi_queries(deps, queries), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result { + let id = msg.id; + Err(ContractError::Std(StdError::generic_err(format!( + "Unknown reply id: {}", + id + )))) +} diff --git a/contracts/common/cw-multicall/src/helpers.rs b/contracts/common/cw-multicall/src/helpers.rs new file mode 100644 index 00000000..935560d5 --- /dev/null +++ b/contracts/common/cw-multicall/src/helpers.rs @@ -0,0 +1,26 @@ +use euclid::msgs::factory::ExecuteMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_json_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } +} diff --git a/contracts/common/cw-multicall/src/lib.rs b/contracts/common/cw-multicall/src/lib.rs new file mode 100644 index 00000000..bfeadc31 --- /dev/null +++ b/contracts/common/cw-multicall/src/lib.rs @@ -0,0 +1,11 @@ +#![allow(clippy::too_many_arguments)] + +pub mod contract; +pub mod migrate; +pub mod query; + +#[cfg(test)] +mod tests; + +#[cfg(not(target_arch = "wasm32"))] +pub mod mock; diff --git a/contracts/common/cw-multicall/src/migrate.rs b/contracts/common/cw-multicall/src/migrate.rs new file mode 100644 index 00000000..cee9a8da --- /dev/null +++ b/contracts/common/cw-multicall/src/migrate.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::{entry_point, DepsMut, Env, Response}; +use euclid::error::ContractError; + +use euclid_utils::msgs::multicall::MigrateMsg; + +/// This is the migrate entry point for the contract. +/// Currently, it does not perform any migration logic and simply returns an empty response. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} diff --git a/contracts/common/cw-multicall/src/mock.rs b/contracts/common/cw-multicall/src/mock.rs new file mode 100644 index 00000000..9d1818ea --- /dev/null +++ b/contracts/common/cw-multicall/src/mock.rs @@ -0,0 +1,50 @@ +#![cfg(not(target_arch = "wasm32"))] + +use crate::contract::{execute, instantiate, query, reply}; +use cosmwasm_std::{Addr, Empty}; +use cw_multi_test::{Contract, ContractWrapper, Executor}; + +use euclid_utils::msgs::multicall::{InstantiateMsg, MultiQuery, MultiQueryResponse, QueryMsg}; +use mock::mock::MockApp; + +pub struct MockEscrow(Addr); +impl MockEscrow { + fn addr(&self) -> &Addr { + &self.0 + } +} + +impl MockEscrow { + pub fn instantiate(app: &mut MockApp, code_id: u64, sender: Addr) -> Self { + let msg = mock_cw_multi_call_msg(); + let res = app.instantiate_contract(code_id, sender, &msg, &[], "Euclid escrow", None); + + Self(res.unwrap()) + } + + pub fn query_multi_queries( + &self, + app: &MockApp, + queries: Vec, + ) -> MultiQueryResponse { + app.wrap() + .query_wasm_smart::( + self.addr().clone().into_string(), + &mock_query_multi_query(queries), + ) + .unwrap() + } +} + +pub fn mock_cw_multi_call() -> Box> { + let contract = ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply); + Box::new(contract) +} + +pub fn mock_cw_multi_call_msg() -> InstantiateMsg { + InstantiateMsg {} +} + +pub fn mock_query_multi_query(queries: Vec) -> QueryMsg { + QueryMsg::MultiQuery { queries } +} diff --git a/contracts/common/cw-multicall/src/query.rs b/contracts/common/cw-multicall/src/query.rs new file mode 100644 index 00000000..bf7ff9e7 --- /dev/null +++ b/contracts/common/cw-multicall/src/query.rs @@ -0,0 +1,67 @@ +use cosmwasm_std::{to_json_binary, to_json_vec, Binary, Deps, QueryResponse}; +use euclid::error::ContractError; +use euclid_utils::msgs::multicall::{MultiQuery, MultiQueryResponse, SingleQueryResponse}; + +/* +/// Executes multiple queries in a single call. +/// +/// This function takes a vector of `MultiQuery` objects and executes each query, +/// collecting the results into a `MultiQueryResponse`. If a query fails, the error +/// is captured in the corresponding `SingleQueryResponse`. +/// +/// # Arguments +/// +/// * `deps` - The dependencies object providing access to storage, API, and Querier. +/// * `queries` - A vector of `MultiQuery` objects representing the queries to be executed. +/// +/// # Returns +/// +/// Returns a `Result` containing a `Binary` representation of `MultiQueryResponse` on success, +/// or a `ContractError` on failure. + +*/ +pub fn query_multi_queries(deps: Deps, queries: Vec) -> Result { + let responses = queries + .iter() + .map(|query| { + let res = query_multi_query(deps, query).map_err(|err| err.to_string()); + SingleQueryResponse { + // We don't want to throw error otherwise the whole multicall will fail + result: res.clone().ok(), + err: res.err(), + } + }) + .collect(); + Ok(to_json_binary(&MultiQueryResponse { responses })?) +} + +/* +Executes a single query from a `MultiQuery` object. + +This function takes a `MultiQuery` object and executes the query it contains, +returning the raw query response. + +# Arguments + +* `deps` - The dependencies object providing access to storage, API, and Querier. +* `query` - A reference to a `MultiQuery` object representing the query to be executed. + +# Returns + +Returns a `Result` containing a `QueryResponse` on success, +or a `ContractError` on failure. +*/ +fn query_multi_query(deps: Deps, query: &MultiQuery) -> Result { + // As we don't know the response type, we will use raw query to get binary response + let raw_query = match query { + MultiQuery::Query(query) => to_json_vec(&query)?, + MultiQuery::RawQuery(query) => query.as_bytes().to_vec(), + }; + let result = deps + .querier + .raw_query(&raw_query) + .into_result() + .map_err(|err| ContractError::new(&err.to_string()))?; + + result.into_result().map_err(|err| ContractError::new(&err)) +} diff --git a/contracts/common/cw-multicall/src/tests.rs b/contracts/common/cw-multicall/src/tests.rs new file mode 100644 index 00000000..96a36b8c --- /dev/null +++ b/contracts/common/cw-multicall/src/tests.rs @@ -0,0 +1,83 @@ +use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_dependencies_with_balances, mock_env, mock_info}, + BalanceResponse, BankQuery, Coin, DepsMut, Env, MessageInfo, +}; +use euclid_utils::msgs::multicall::{InstantiateMsg, MultiQuery, MultiQueryResponse, QueryMsg}; + +use crate::contract::{instantiate, query}; + +fn init_cw_multicall(deps: DepsMut, env: Env, info: MessageInfo) { + let msg = InstantiateMsg {}; + let res = instantiate(deps, env, info, msg); + assert!(res.is_ok()) +} + +#[test] +fn test_instantiation() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("creator", &[]); + init_cw_multicall(deps.as_mut(), env, info) +} + +#[test] +fn test_multiquery_call() { + let coin = Coin::new(10000, "test"); + let info = mock_info("creator", &[]); + let mut deps = mock_dependencies_with_balances(&[("creator", &[coin.clone()])]); + let env = mock_env(); + + init_cw_multicall(deps.as_mut(), env.clone(), info); + + let mut queries: Vec = vec![]; + let bank_query = BankQuery::Balance { + address: "creator".to_string(), + denom: coin.denom.clone(), + }; + + queries.push(MultiQuery::Query(bank_query.into())); + // Add a raw query for the same bank query + let raw_query = format!( + "{{\"bank\":{{\"balance\":{{\"address\":\"{}\",\"denom\":\"{}\"}}}}}}", + "creator", coin.denom + ); + queries.push(MultiQuery::RawQuery(raw_query)); + + let msg = QueryMsg::MultiQuery { + queries: queries.clone(), + }; + let result = query(deps.as_ref(), env.clone(), msg.clone()).unwrap(); + let result: MultiQueryResponse = from_json(result).unwrap(); + + assert_eq!( + result.responses.len(), + queries.len(), + "Queries == Responses" + ); + + let bank_response = result.responses.first().unwrap().clone(); + + assert_eq!(bank_response.err, None, "Successful Query"); + + assert_eq!( + from_json::(&bank_response.result.unwrap()).unwrap(), + BalanceResponse { + amount: coin.clone() + }, + "Balance is same as assignined coin balance" + ); + + // Check the second query response (raw bank query) + let raw_bank_response = result.responses.get(1).unwrap().clone(); + + assert_eq!(raw_bank_response.err, None, "Successful Raw Query"); + + let raw_balance_response: BalanceResponse = + from_json(raw_bank_response.result.unwrap()).unwrap(); + assert_eq!( + raw_balance_response, + BalanceResponse { amount: coin }, + "Raw query balance is same as assigned coin balance" + ); +} diff --git a/contracts/forwarding/astroport-forwarding/.cargo/config.toml b/contracts/forwarding/astroport-forwarding/.cargo/config.toml new file mode 100644 index 00000000..af5698e5 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/forwarding/astroport-forwarding/.editorconfig b/contracts/forwarding/astroport-forwarding/.editorconfig new file mode 100644 index 00000000..3d36f20b --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.rs] +indent_size = 4 diff --git a/contracts/forwarding/astroport-forwarding/.gitignore b/contracts/forwarding/astroport-forwarding/.gitignore new file mode 100644 index 00000000..9095deaa --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/.gitignore @@ -0,0 +1,16 @@ +# Build results +/target +/schema + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/forwarding/astroport-forwarding/Cargo.toml b/contracts/forwarding/astroport-forwarding/Cargo.toml new file mode 100644 index 00000000..f72c9f27 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "astroport-forwarding" +version = "0.2.1" +authors = ["Anshudhar Kumar Singh "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# 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.15.0 +""" + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true, features = [ + "cosmwasm_1_2", + # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher + # "cosmwasm_1_4", + "ibc3", +] } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, default-features = false, features = ["derive"] } +thiserror = { workspace = true } +euclid = { workspace = true } +forwarding = { workspace = true } +cw-orch = "=0.24.1" +astroport = { git = "https://github.com/astroport-fi/astroport-core", branch = "main" } +cw-utils = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +cw-multi-test = { workspace = true } +mock = { workspace = true } diff --git a/contracts/forwarding/astroport-forwarding/LICENSE b/contracts/forwarding/astroport-forwarding/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contracts/forwarding/astroport-forwarding/NOTICE b/contracts/forwarding/astroport-forwarding/NOTICE new file mode 100644 index 00000000..e7d57816 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/NOTICE @@ -0,0 +1,13 @@ +Copyright 2024 gachouchani1999 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/forwarding/astroport-forwarding/README.md b/contracts/forwarding/astroport-forwarding/README.md new file mode 100644 index 00000000..b4b01a7e --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/README.md @@ -0,0 +1,4 @@ +# Escrow Contract +The Escrow smart contract is a simple contract that holds one type of token. Each integrated chain will have these escrows deployed, holding the liquidity for the tokens. Refer to the following resources to learn more about the Escrow Contract: +- [Architecture](https://docs.euclidprotocol.io/docs/Architecture%20Overview/Architecture/Integrated%20Chains%20Layer/escrows) +- [Detailed Breakdown of the Escrow's available messages](https://docs.euclidprotocol.io/docs/Euclid%20Smart%20Contracts/CosmWasm/Escrow) \ No newline at end of file diff --git a/contracts/forwarding/astroport-forwarding/src/bin/schema.rs b/contracts/forwarding/astroport-forwarding/src/bin/schema.rs new file mode 100644 index 00000000..a162481d --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/bin/schema.rs @@ -0,0 +1,24 @@ +use std::env::current_dir; + +use cosmwasm_schema::{export_schema_with_title, schema_for, write_api}; +use forwarding::msgs::astroport::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use forwarding::msgs::cw20::Cw20HookMsg; +use forwarding::msgs::euclid_receive::AstroportEuclidReceiveHook; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + out_dir.push("raw"); + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } + + export_schema_with_title(&schema_for!(Cw20HookMsg), &out_dir, "cw20receive"); + export_schema_with_title( + &schema_for!(AstroportEuclidReceiveHook), + &out_dir, + "euclid-receive", + ); +} diff --git a/contracts/forwarding/astroport-forwarding/src/contract.rs b/contracts/forwarding/astroport-forwarding/src/contract.rs new file mode 100644 index 00000000..c8e240dc --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/contract.rs @@ -0,0 +1,79 @@ +use std::borrow::BorrowMut; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ensure, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError}; + +use cw2::set_contract_version; +use euclid::{error::ContractError, token::TokenType}; + +use crate::{ + execute::{execute_cw20_receive, receive_euclid_native, swap}, + reply::{on_astro_swap_reply, ASTRO_SWAP_REPLY_ID}, + state::{State, STATE}, +}; + +use forwarding::msgs::astroport::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:astroport-forwarding"; +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 { + let state = State { + astro_router_address: msg.astro_router.clone(), + }; + STATE.save(deps.storage, &state)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("astro_router", msg.astro_router)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::EuclidReceive(msg) => { + receive_euclid_native(deps.borrow_mut(), &env, &info, msg) + } + ExecuteMsg::Receive(msg) => execute_cw20_receive(deps.borrow_mut(), &env, &info, msg), + ExecuteMsg::Swap(swap_msg) => { + ensure!( + info.funds.len() == 1, + ContractError::new("only one token is supported") + ); + let from_token = TokenType::Native { + denom: info.funds[0].denom.to_string(), + }; + let from_amount = info.funds[0].amount; + + swap(deps.borrow_mut(), &env, swap_msg, from_token, from_amount) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> Result { + match msg {} +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match msg.id { + ASTRO_SWAP_REPLY_ID => on_astro_swap_reply(deps, env, msg), + id => Err(ContractError::Std(StdError::generic_err(format!( + "Unknown reply id: {}", + id + )))), + } +} diff --git a/contracts/forwarding/astroport-forwarding/src/execute.rs b/contracts/forwarding/astroport-forwarding/src/execute.rs new file mode 100644 index 00000000..0a4f1829 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/execute.rs @@ -0,0 +1,154 @@ +use cosmwasm_std::{ + coin, ensure, from_json, to_json_binary, DepsMut, Env, MessageInfo, Response, SubMsg, Uint128, + WasmMsg, +}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; +use euclid::{ + error::ContractError, events::simple_event, msgs::hook::EuclidReceive, token::TokenType, +}; +use forwarding::msgs::{ + astroport::SwapMsg, cw20::Cw20HookMsg, euclid_receive::AstroportEuclidReceiveHook, +}; + +use astroport::router::ExecuteMsg as AstroportExecuteMsg; + +use crate::{ + reply::ASTRO_SWAP_REPLY_ID, + state::{ForwardingState, FORWARDING_STATE, STATE}, +}; + +pub fn execute_cw20_receive( + deps: &mut DepsMut, + env: &Env, + info: &MessageInfo, + receive_msg: Cw20ReceiveMsg, +) -> Result { + let amount = receive_msg.amount; + let from_token = TokenType::Smart { + contract_address: info.sender.to_string(), + }; + + let msg: Cw20HookMsg = from_json(receive_msg.msg)?; + match msg { + Cw20HookMsg::EuclidReceive(euclid_receive) => receive_euclid_cw20( + deps, + env, + info, + receive_msg.sender.to_string(), + euclid_receive, + amount, + ), + Cw20HookMsg::Swap(swap_msg) => swap(deps, env, swap_msg, from_token, amount), + } +} + +pub fn receive_euclid_native( + deps: &mut DepsMut, + env: &Env, + info: &MessageInfo, + euclid_receive: EuclidReceive, +) -> Result { + match from_json::(euclid_receive.data.clone())? { + AstroportEuclidReceiveHook::Swap(swap_msg) => { + let response = crate::contract::execute( + deps.branch(), + env.clone(), + info.clone(), + forwarding::msgs::astroport::ExecuteMsg::Swap(swap_msg), + )?; + let event = simple_event().add_attribute( + "meta", + euclid_receive.meta.clone().unwrap_or("no_meta".to_string()), + ); + Ok(response.add_event(event)) + } + } +} + +pub fn receive_euclid_cw20( + deps: &mut DepsMut, + env: &Env, + _info: &MessageInfo, + sender: String, + euclid_receive: EuclidReceive, + amount: Uint128, +) -> Result { + match from_json::(euclid_receive.data.clone())? { + AstroportEuclidReceiveHook::Swap(swap_msg) => { + let from_token = TokenType::Smart { + contract_address: sender.to_string(), + }; + let response = swap(deps, env, swap_msg, from_token, amount)?; + let event = simple_event().add_attribute( + "meta", + euclid_receive.meta.clone().unwrap_or("no_meta".to_string()), + ); + Ok(response.add_event(event)) + } + } +} + +pub fn swap( + deps: &mut DepsMut, + env: &Env, + swap_msg: SwapMsg, + from_token: TokenType, + from_amount: Uint128, +) -> Result { + let state = STATE.load(deps.storage)?; + + let operations = swap_msg.operations.clone(); + ensure!( + !operations.is_empty(), + ContractError::new("min 1 operation is required") + ); + + let astro_execute_msg = AstroportExecuteMsg::ExecuteSwapOperations { + operations, + minimum_receive: Some(swap_msg.minimum_receive), + // Contract will receive the tokens + to: Some(env.contract.address.to_string()), + max_spread: swap_msg.max_spread, + }; + + let previous_balance = swap_msg + .to_token + .get_balance(deps.as_ref(), env.contract.address.to_string())?; + + FORWARDING_STATE.save( + deps.storage, + &ForwardingState { + from_token: from_token.clone(), + from_amount, + previous_balance, + swap_msg, + }, + )?; + + let msg = match &from_token { + TokenType::Native { denom } => WasmMsg::Execute { + contract_addr: state.astro_router_address.to_string(), + msg: to_json_binary(&astro_execute_msg)?, + funds: vec![coin(from_amount.u128(), denom)], + }, + TokenType::Smart { contract_address } => { + let send_msg = Cw20ExecuteMsg::Send { + contract: state.astro_router_address.to_string(), + amount: from_amount, + msg: to_json_binary(&astro_execute_msg)?, + }; + WasmMsg::Execute { + contract_addr: contract_address.to_string(), + msg: to_json_binary(&send_msg)?, + funds: vec![], + } + } + _ => return Err(ContractError::new("unsupported token type")), + }; + + Ok(Response::new() + .add_attribute("dex", "astroport") + .add_attribute("start_swap_amount", from_amount) + .add_attribute("start_swap_token", from_token.get_key()) + .add_submessage(SubMsg::reply_always(msg, ASTRO_SWAP_REPLY_ID))) +} diff --git a/contracts/forwarding/astroport-forwarding/src/lib.rs b/contracts/forwarding/astroport-forwarding/src/lib.rs new file mode 100644 index 00000000..fa7c3584 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/lib.rs @@ -0,0 +1,8 @@ +#![allow(clippy::too_many_arguments)] + +pub mod contract; +pub mod execute; +pub mod migrate; +pub mod query; +pub mod reply; +pub mod state; diff --git a/contracts/forwarding/astroport-forwarding/src/migrate.rs b/contracts/forwarding/astroport-forwarding/src/migrate.rs new file mode 100644 index 00000000..6dea066b --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/migrate.rs @@ -0,0 +1,9 @@ +use cosmwasm_std::{entry_point, DepsMut, Env, Response}; +use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; + +/// This is the migrate entry point for the contract. +/// Currently, it does not perform any migration logic and simply returns an empty response. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} diff --git a/contracts/forwarding/astroport-forwarding/src/query.rs b/contracts/forwarding/astroport-forwarding/src/query.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/query.rs @@ -0,0 +1 @@ + diff --git a/contracts/forwarding/astroport-forwarding/src/reply.rs b/contracts/forwarding/astroport-forwarding/src/reply.rs new file mode 100644 index 00000000..fb6e630c --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/reply.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::{ensure, to_json_binary, DepsMut, Env, Reply, Response, SubMsgResult}; +use euclid::{error::ContractError, msgs::hook::EuclidReceiverMsg}; + +use crate::state::{ForwardingState, FORWARDING_STATE}; + +pub const ASTRO_SWAP_REPLY_ID: u64 = 1; + +pub fn on_astro_swap_reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match msg.result.clone() { + SubMsgResult::Err(err) => Err(ContractError::new(&format!( + "Astroport swap failed: {}", + err + ))), + SubMsgResult::Ok(..) => { + let ForwardingState { + from_token, + from_amount, + previous_balance, + swap_msg, + } = FORWARDING_STATE.load(deps.storage)?; + + FORWARDING_STATE.remove(deps.storage); + + let new_balance = swap_msg + .to_token + .get_balance(deps.as_ref(), env.contract.address.to_string())?; + + let swap_amount = new_balance.checked_sub(previous_balance)?; + + ensure!( + swap_amount >= swap_msg.minimum_receive, + ContractError::MinReceived { + expected: swap_msg.minimum_receive, + received: swap_amount, + } + ); + + let forwading_msg = match swap_msg.forwarding_msg { + Some(forwarding_msg) => Some(to_json_binary(&EuclidReceiverMsg::EuclidReceive( + forwarding_msg, + ))?), + None => None, + }; + + let transfer_msg = swap_msg.to_token.create_transfer_msg( + swap_amount, + swap_msg.recipient, + None, + forwading_msg, + )?; + + Ok(Response::new() + .add_attribute("action", "reply_astro_swap") + .add_attribute("dex", "astroport") + .add_attribute("complete_swap_in_amount", from_amount) + .add_attribute("complete_swap_in_token", from_token.get_key()) + .add_attribute("complete_swap_out_amount", swap_amount) + .add_attribute("complete_swap_out_token", swap_msg.to_token.get_key()) + .add_message(transfer_msg)) + } + } +} diff --git a/contracts/forwarding/astroport-forwarding/src/state.rs b/contracts/forwarding/astroport-forwarding/src/state.rs new file mode 100644 index 00000000..ef1b6e07 --- /dev/null +++ b/contracts/forwarding/astroport-forwarding/src/state.rs @@ -0,0 +1,21 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Uint128}; +use cw_storage_plus::Item; +use euclid::token::TokenType; +use forwarding::msgs::astroport::SwapMsg; +#[cw_serde] +pub struct State { + pub astro_router_address: Addr, +} + +pub const STATE: Item = Item::new("state"); + +#[cw_serde] +pub struct ForwardingState { + pub from_token: TokenType, + pub from_amount: Uint128, + pub previous_balance: Uint128, + pub swap_msg: SwapMsg, +} + +pub const FORWARDING_STATE: Item = Item::new("forwarding_state"); diff --git a/contracts/hub/router/Cargo.toml b/contracts/hub/router/Cargo.toml index 8d18b1ac..23f39d5c 100644 --- a/contracts/hub/router/Cargo.toml +++ b/contracts/hub/router/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "router" -version = "0.1.0" -authors = ["anshudhar "] +version = "0.2.3" +authors = [ + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -50,6 +53,7 @@ serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } euclid-ibc = { workspace = true } +cw-orch = "=0.24.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/hub/router/src/contract.rs b/contracts/hub/router/src/contract.rs index b95486f7..c388f5c0 100644 --- a/contracts/hub/router/src/contract.rs +++ b/contracts/hub/router/src/contract.rs @@ -1,36 +1,34 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, SubMsg, - WasmMsg, + ensure, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, + SubMsg, WasmMsg, }; use cw2::set_contract_version; -use euclid::chain::ChainUid; use euclid::error::ContractError; use euclid_ibc::msg::HUB_IBC_EXECUTE_MSG_QUEUE_RANGE; use crate::execute::{ execute_deregister_chain, execute_native_receive_callback, execute_register_factory, execute_release_escrow, execute_reregister_chain, execute_update_factory_channel, - execute_update_lock, execute_update_vlp_code_id, execute_withdraw_voucher, + execute_update_lock, execute_update_router_state, execute_withdraw_voucher, }; use crate::ibc::ack_and_timeout::ibc_ack_packet_internal_call; use crate::ibc::receive::ibc_receive_internal_call; use crate::query::{ - self, query_all_chains, query_all_tokens, query_all_vlps, query_chain, - query_simulate_escrow_release, query_state, query_token_escrows, query_vlp, + self, query_all_chains, query_all_escrows, query_all_tokens, query_all_vlps, query_chain, + query_simulate_escrow_release, query_state, query_token_denoms, query_token_escrows, query_vlp, }; use crate::reply::{ self, ADD_LIQUIDITY_REPLY_ID, IBC_ACK_AND_TIMEOUT_REPLY_ID, IBC_RECEIVE_REPLY_ID, - REMOVE_LIQUIDITY_REPLY_ID, SWAP_REPLY_ID, VIRTUAL_BALANCE_BURN_REPLY_ID, - VIRTUAL_BALANCE_INSTANTIATE_REPLY_ID, VIRTUAL_BALANCE_MINT_REPLY_ID, - VIRTUAL_BALANCE_TRANSFER_REPLY_ID, VLP_INSTANTIATE_REPLY_ID, VLP_POOL_REGISTER_REPLY_ID, + REMOVE_LIQUIDITY_REPLY_ID, SWAP_REPLY_ID, VIRTUAL_BALANCE_INSTANTIATE_REPLY_ID, + VLP_INSTANTIATE_REPLY_ID, VLP_POOL_REGISTER_REPLY_ID, }; use crate::state::{State, DEREGISTERED_CHAINS, STATE}; use euclid::msgs::router::{ExecuteMsg, InstantiateMsg, QueryMsg}; // version info for migration info -const CONTRACT_NAME: &str = "crates.io:factory"; +const CONTRACT_NAME: &str = "crates.io:router"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -67,8 +65,7 @@ pub fn instantiate( VIRTUAL_BALANCE_INSTANTIATE_REPLY_ID, ); - let empty_chains: Vec = vec![]; - DEREGISTERED_CHAINS.save(deps.storage, &empty_chains)?; + DEREGISTERED_CHAINS.save(deps.storage, &vec![])?; Ok(Response::new() .add_attribute("method", "instantiate") @@ -84,73 +81,80 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { // If the contract is locked and the message isn't UpdateLock, return error - let locked = STATE.load(deps.storage)?.locked; - if locked { - if let ExecuteMsg::UpdateLock {} = msg { - execute_update_lock(deps, info) - } else if let ExecuteMsg::ReregisterChain { chain } = msg { - execute_reregister_chain(deps, info, chain) - } else if let ExecuteMsg::DeregisterChain { chain } = msg { - execute_deregister_chain(deps, info, chain) - } else { - Err(ContractError::ContractLocked {}) - } - } else { - match msg { - ExecuteMsg::ReregisterChain { chain } => execute_reregister_chain(deps, info, chain), - ExecuteMsg::DeregisterChain { chain } => execute_deregister_chain(deps, info, chain), - ExecuteMsg::UpdateFactoryChannel { channel, chain_uid } => { - execute_update_factory_channel(&mut deps, env, info, channel, chain_uid) - } - ExecuteMsg::UpdateVLPCodeId { new_vlp_code_id } => { - execute_update_vlp_code_id(deps, info, new_vlp_code_id) - } - ExecuteMsg::RegisterFactory { - chain_uid, - chain_info, - } => execute_register_factory(&mut deps, env, info, chain_uid, chain_info), - ExecuteMsg::ReleaseEscrowInternal { - sender, - token, - amount, - cross_chain_addresses, - timeout, - tx_id, - } => execute_release_escrow( - &mut deps, - env, - info, - sender, - token, - amount, - cross_chain_addresses, - timeout, - tx_id, - ), - ExecuteMsg::WithdrawVoucher { - token, - amount, - cross_chain_addresses, - timeout, - } => execute_withdraw_voucher( - &mut deps, - env, - info, - token, - amount, - cross_chain_addresses, - timeout, - ), - ExecuteMsg::IbcCallbackReceive { receive_msg } => { - ibc_receive_internal_call(&mut deps, env, info, receive_msg) - } - ExecuteMsg::IbcCallbackAckAndTimeout { ack } => { - ibc_ack_packet_internal_call(deps, env, ack) - } - ExecuteMsg::UpdateLock {} => execute_update_lock(deps, info), - ExecuteMsg::NativeReceiveCallback { msg, chain_uid } => { - execute_native_receive_callback(&mut deps, env, info, chain_uid, msg) + match msg { + ExecuteMsg::UpdateLock {} => execute_update_lock(deps, info), + ExecuteMsg::ReregisterChain { chain } => execute_reregister_chain(deps, info, chain), + ExecuteMsg::DeregisterChain { chain } => execute_deregister_chain(deps, info, chain), + _ => { + ensure!( + !STATE.load(deps.storage)?.locked, + ContractError::ContractLocked {} + ); + match msg { + ExecuteMsg::UpdateFactoryChannel { channel, chain_uid } => { + execute_update_factory_channel(&mut deps, env, info, channel, chain_uid) + } + ExecuteMsg::RegisterFactory { + chain_uid, + chain_info, + } => execute_register_factory(&mut deps, env, info, chain_uid, chain_info), + ExecuteMsg::ReleaseEscrowInternal { + sender, + token, + amount, + cross_chain_addresses, + timeout, + tx_id, + } => execute_release_escrow( + &mut deps, + env, + info, + sender, + token, + amount, + cross_chain_addresses, + timeout, + tx_id, + ), + ExecuteMsg::WithdrawVoucher { + token, + amount, + cross_chain_addresses, + timeout, + } => execute_withdraw_voucher( + &mut deps, + env, + info, + token, + amount, + cross_chain_addresses, + timeout, + ), + ExecuteMsg::IbcCallbackReceive { receive_msg } => { + ibc_receive_internal_call(&mut deps, env, info, receive_msg) + } + ExecuteMsg::IbcCallbackAckAndTimeout { ack } => { + ibc_ack_packet_internal_call(deps, info, env, ack) + } + ExecuteMsg::UpdateLock {} => execute_update_lock(deps, info), + ExecuteMsg::NativeReceiveCallback { msg, chain_uid } => { + execute_native_receive_callback(&mut deps, env, info, chain_uid, msg) + } + ExecuteMsg::UpdateRouterState { + admin, + vlp_code_id, + virtual_balance_address, + locked, + } => execute_update_router_state( + deps, + info, + admin, + vlp_code_id, + virtual_balance_address, + locked, + ), + _ => Err(ContractError::UnreachableCode {}), } } } @@ -173,7 +177,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { query_token_escrows(deps, token, pagination) } + QueryMsg::QueryAllEscrows { pagination } => query_all_escrows(deps, pagination), QueryMsg::QueryAllTokens { pagination } => query_all_tokens(deps, pagination), + QueryMsg::QueryTokenDenoms { token } => query_token_denoms(deps, token), } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -192,15 +198,10 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result reply::on_add_liquidity_reply(deps, msg), REMOVE_LIQUIDITY_REPLY_ID => reply::on_remove_liquidity_reply(deps, env, msg), SWAP_REPLY_ID => reply::on_swap_reply(deps, env, msg), - VIRTUAL_BALANCE_INSTANTIATE_REPLY_ID => { reply::on_virtual_balance_instantiate_reply(deps, msg) } - VIRTUAL_BALANCE_MINT_REPLY_ID => reply::on_virtual_balance_mint_reply(deps, msg), - VIRTUAL_BALANCE_BURN_REPLY_ID => reply::on_virtual_balance_burn_reply(deps, msg), - VIRTUAL_BALANCE_TRANSFER_REPLY_ID => reply::on_virtual_balance_transfer_reply(deps, msg), - IBC_ACK_AND_TIMEOUT_REPLY_ID => reply::on_ibc_ack_and_timeout_reply(deps, msg), IBC_RECEIVE_REPLY_ID => reply::on_ibc_receive_reply(deps, msg), diff --git a/contracts/hub/router/src/execute.rs b/contracts/hub/router/src/execute.rs index efbfae12..bab0b4de 100644 --- a/contracts/hub/router/src/execute.rs +++ b/contracts/hub/router/src/execute.rs @@ -1,10 +1,10 @@ use cosmwasm_std::{ - ensure, from_json, to_json_binary, Binary, CosmosMsg, DepsMut, Env, IbcMsg, IbcTimeout, + ensure, from_json, to_json_binary, Addr, Binary, CosmosMsg, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, Response, SubMsg, Uint128, WasmMsg, }; use euclid::{ - chain::{Chain, ChainUid, CrossChainUser, CrossChainUserWithLimit}, + chain::{Chain, ChainUid, CrossChainUser, CrossChainUserWithLimit, Limit}, error::ContractError, events::{tx_event, TxType}, msgs::{ @@ -13,38 +13,20 @@ use euclid::{ }, timeout::get_timeout, token::Token, - utils::generate_tx, + utils::tx::generate_tx, virtual_balance::BalanceKey, }; use euclid_ibc::msg::{ChainIbcExecuteMsg, HubIbcExecuteMsg}; use crate::{ ibc::receive, - reply::VIRTUAL_BALANCE_BURN_REPLY_ID, + query::verify_cross_chain_addresses, state::{ - CHAIN_UID_TO_CHAIN, CHANNEL_TO_CHAIN_UID, DEREGISTERED_CHAINS, ESCROW_BALANCES, STATE, + State, CHAIN_UID_TO_CHAIN, CHANNEL_TO_CHAIN_UID, DEREGISTERED_CHAINS, ESCROW_BALANCES, + STATE, TOKEN_DENOMS, }, }; -// Function to update the pool code ID -pub fn execute_update_vlp_code_id( - deps: DepsMut, - info: MessageInfo, - new_vlp_code_id: u64, -) -> Result { - let mut state = STATE.load(deps.storage)?; - - ensure!(info.sender == state.admin, ContractError::Unauthorized {}); - - state.vlp_code_id = new_vlp_code_id; - - STATE.save(deps.storage, &state)?; - - Ok(Response::new() - .add_attribute("method", "update_pool_code_id") - .add_attribute("new_vlp_code_id", new_vlp_code_id.to_string())) -} - pub fn execute_update_lock(deps: DepsMut, info: MessageInfo) -> Result { let mut state = STATE.load(deps.storage)?; ensure!(info.sender == state.admin, ContractError::Unauthorized {}); @@ -114,7 +96,6 @@ pub fn execute_register_factory( chain_info: RegisterFactoryChainType, ) -> Result { let chain_uid = chain_uid.validate()?.to_owned(); - ensure!( !CHAIN_UID_TO_CHAIN.has(deps.storage, chain_uid.clone()), ContractError::new("Factory already exists") @@ -133,7 +114,6 @@ pub fn execute_register_factory( ContractError::new("Cannot use VSL chain uid") ); - // TODO: Add check for existing chain ids let state = STATE.load(deps.storage)?; ensure!(info.sender == state.admin, ContractError::Unauthorized {}); @@ -156,7 +136,6 @@ pub fn execute_register_factory( data: to_json_binary(&msg)?, timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), }; - Ok(response .add_attribute("channel", ibc_info.channel) .add_attribute("timeout", timeout.to_string()) @@ -248,6 +227,14 @@ pub fn execute_withdraw_voucher( cross_chain_addresses: Vec, timeout: Option, ) -> Result { + verify_cross_chain_addresses( + deps.as_ref(), + cross_chain_addresses + .clone() + .into_iter() + .map(|x| x.user) + .collect(), + )?; let cross_chain_user = CrossChainUser { chain_uid: ChainUid::vsl_chain_uid()?, address: info.sender.to_string(), @@ -271,6 +258,7 @@ pub fn execute_withdraw_voucher( .add_attribute("method", "withdraw_voucher")) } +#[allow(clippy::too_many_arguments)] pub fn execute_release_escrow( deps: &mut DepsMut, env: Env, @@ -283,12 +271,13 @@ pub fn execute_release_escrow( timeout: Option, tx_id: String, ) -> Result { - let state = STATE.load(deps.storage)?; ensure!( info.sender == env.contract.address, ContractError::Unauthorized {} ); + let state = STATE.load(deps.storage)?; + let virtual_balance_address = state .virtual_balance_address .ok_or(ContractError::new("virtual balance doesn't exist"))? @@ -318,36 +307,72 @@ pub fn execute_release_escrow( sender.address.as_str(), TxType::EscrowRelease, )) - .add_attribute("tx_id", tx_id); + .add_attribute("tx_id", tx_id.clone()); let timeout = get_timeout(timeout)?; let mut release_msgs: Vec = vec![]; let mut cross_chain_addresses_iterator = cross_chain_addresses.into_iter().peekable(); let mut remaining_withdraw_amount = amount; + let token_denoms = TOKEN_DENOMS.load(deps.storage, token.clone())?; let mut transfer_amount = Uint128::zero(); // Ensure that the amount desired doesn't exceed the current balance while !remaining_withdraw_amount.is_zero() && cross_chain_addresses_iterator.peek().is_some() { let cross_chain_address = cross_chain_addresses_iterator .next() - .ok_or(ContractError::new("Cross Chain Address Iter Faiiled"))?; + .ok_or(ContractError::new("Cross Chain Address Iter Failed"))?; let chain = CHAIN_UID_TO_CHAIN.load(deps.storage, cross_chain_address.user.chain_uid.clone())?; + if let Some(ref preferred_denom) = cross_chain_address.preferred_denom { + // Ensure that the preferred denom is valid + ensure!( + token_denoms + .iter() + .any(|x| x.token_type == preferred_denom.clone() + && x.chain_uid == cross_chain_address.user.chain_uid), + ContractError::InvalidDenom {} + ); + } + let escrow_key = ESCROW_BALANCES.key((token.clone(), cross_chain_address.user.chain_uid.clone())); let escrow_balance = escrow_key .may_load(deps.storage)? .unwrap_or(Uint128::zero()); - let release_amount = if remaining_withdraw_amount.ge(&escrow_balance) { + let mut release_amount = if remaining_withdraw_amount.ge(&escrow_balance) { escrow_balance } else { remaining_withdraw_amount }; - let release_amount = release_amount.min(cross_chain_address.limit.unwrap_or(Uint128::MAX)); + match cross_chain_address.limit { + Some(Limit::LessThanOrEqual(limit)) => { + release_amount = release_amount.min(limit); + } + Some(Limit::Equal(limit)) => { + ensure!( + release_amount.ge(&limit), + ContractError::InsufficientAmount { + min_amount: limit, + amount: release_amount + } + ); + release_amount = limit; + } + Some(Limit::GreaterThanOrEqual(limit)) => { + ensure!( + release_amount.ge(&limit), + ContractError::AmountMismatch { + expected: limit, + received: release_amount + } + ); + } + _ => {} + } if release_amount.is_zero() { continue; @@ -361,18 +386,18 @@ pub fn execute_release_escrow( let send_msg = HubIbcExecuteMsg::ReleaseEscrow { sender: sender.clone(), amount: release_amount, + recipient: cross_chain_address.clone(), token: token.clone(), - to_address: cross_chain_address.user.address.clone(), // We can't use same tx id because it might conflict with pending requests on receiving chain tx_id: generate_tx(deps.branch(), &env, &sender)?, - chain_uid: cross_chain_address.user.chain_uid.clone(), } .to_msg(deps, &env, chain, timeout)?; response = response.add_attribute( format!( - "release_escrow_expected_{sender}", - sender = cross_chain_address.user.to_sender_string() + "release_escrow_expected_{token}_{sender}", + sender = cross_chain_address.user.to_sender_string(), + token = token ), release_amount, ); @@ -383,7 +408,7 @@ pub fn execute_release_escrow( ensure!( transfer_amount.checked_add(remaining_withdraw_amount)? == amount, - ContractError::new("Amount mismatch after trasnfer calculations") + ContractError::new("Amount mismatch after transfer calculations") ); if !transfer_amount.is_zero() { @@ -401,16 +426,14 @@ pub fn execute_release_escrow( msg: to_json_binary(&burn_virtual_balance_msg)?, funds: vec![], }); - response = response.add_submessage(SubMsg::reply_always( - burn_virtual_balance_msg, - VIRTUAL_BALANCE_BURN_REPLY_ID, - )); + response = response.add_message(burn_virtual_balance_msg); } Ok(response - .add_attribute("method", "release_escrow") + .add_attribute("method", "release_escrow_initiate") + .add_attribute("token", token.to_string()) .add_attribute("release_expected", amount) - .add_attribute("actual_released", transfer_amount) + .add_attribute("release_initiated", transfer_amount) .add_submessages(release_msgs)) } @@ -431,3 +454,55 @@ pub fn execute_native_receive_callback( ensure!(chain.factory == info.sender, ContractError::Unauthorized {}); receive::reusable_internal_call(deps, env, info, msg, chain_uid) } + +pub fn execute_update_router_state( + deps: DepsMut, + info: MessageInfo, + admin: Option, + vlp_code_id: Option, + virtual_balance_address: Option, + locked: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + ensure!(info.sender == state.admin, ContractError::Unauthorized {}); + + let verified_virtual_balance_address: Result, ContractError> = + virtual_balance_address + .as_ref() + .map_or(Ok(state.virtual_balance_address), |address| { + let validated_addr = Some(deps.api.addr_validate(address.as_str())?); + Ok(validated_addr) + }); + + // Validate Admin Address if provided + let verified_admin = if let Some(ref admin) = admin { + deps.api.addr_validate(admin.as_str())?.to_string() + } else { + state.admin + }; + + let state = State { + admin: verified_admin, + vlp_code_id: vlp_code_id.unwrap_or(state.vlp_code_id), + virtual_balance_address: verified_virtual_balance_address?, + locked: locked.unwrap_or(state.locked), + }; + + STATE.save(deps.storage, &state)?; + + Ok(Response::new() + .add_attribute("method", "update_state") + .add_attribute("admin", admin.unwrap_or("unchanged".to_string())) + .add_attribute( + "vlp_code_id", + vlp_code_id.map_or("unchanged".to_string(), |code_id| code_id.to_string()), + ) + .add_attribute( + "virtual_balance_address", + virtual_balance_address.map_or("unchanged".to_string(), |addr| addr.to_string()), + ) + .add_attribute( + "locked", + locked.map_or("unchanged".to_string(), |locked_val| locked_val.to_string()), + )) +} diff --git a/contracts/hub/router/src/ibc/ack_and_timeout.rs b/contracts/hub/router/src/ibc/ack_and_timeout.rs index 69a6cc2e..8ded3af3 100644 --- a/contracts/hub/router/src/ibc/ack_and_timeout.rs +++ b/contracts/hub/router/src/ibc/ack_and_timeout.rs @@ -1,8 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_json, Binary, CosmosMsg, DepsMut, Env, IbcBasicResponse, IbcPacketAckMsg, - IbcPacketTimeoutMsg, Response, StdError, StdResult, SubMsg, Uint128, WasmMsg, + ensure, from_json, Binary, CosmosMsg, DepsMut, Env, IbcBasicResponse, IbcPacketAckMsg, + IbcPacketTimeoutMsg, MessageInfo, Response, StdError, StdResult, SubMsg, Uint128, WasmMsg, }; use cosmwasm_std::{to_json_binary, IbcAcknowledgement}; use euclid::chain::{Chain, ChainType, ChainUid, CrossChainUser}; @@ -48,9 +48,14 @@ pub fn ibc_packet_ack( pub fn ibc_ack_packet_internal_call( deps: DepsMut, + info: MessageInfo, env: Env, ack: IbcPacketAckMsg, ) -> Result { + ensure!( + info.sender == env.contract.address, + ContractError::Unauthorized {} + ); // Parse the ack based on request let msg: HubIbcExecuteMsg = from_json(ack.original_packet.data)?; @@ -81,11 +86,10 @@ pub fn reusable_internal_ack_call( token, tx_id, sender, - chain_uid, .. } => { let res = from_json(ack)?; - ibc_ack_release_escrow(deps, env, chain_uid, sender, amount, token, res, tx_id) + ibc_ack_release_escrow(deps, env, sender, amount, token, res, tx_id) } HubIbcExecuteMsg::UpdateFactoryChannel { chain_uid, tx_id } => { let res = from_json(ack)?; @@ -224,10 +228,10 @@ pub fn ibc_ack_update_factory_channel( } } +#[allow(clippy::too_many_arguments)] pub fn ibc_ack_release_escrow( deps: DepsMut, _env: Env, - chain_uid: ChainUid, sender: CrossChainUser, amount: Uint128, token: Token, @@ -263,7 +267,7 @@ pub fn ibc_ack_release_escrow( let mint_msg = VirtualBalanceExecuteMsg::Mint(ExecuteMint { amount, balance_key: BalanceKey { - cross_chain_user: sender, + cross_chain_user: sender.clone(), token_id: token.to_string(), }, }); @@ -274,7 +278,7 @@ pub fn ibc_ack_release_escrow( }); // Escrow release is failed, add the old escrow balance again - let escrow_key = ESCROW_BALANCES.key((token, chain_uid)); + let escrow_key = ESCROW_BALANCES.key((token, sender.chain_uid)); let new_balance = escrow_key.load(deps.storage)?.checked_add(amount)?; escrow_key.save(deps.storage, &new_balance)?; diff --git a/contracts/hub/router/src/ibc/receive.rs b/contracts/hub/router/src/ibc/receive.rs index 638f2707..b63d35c1 100644 --- a/contracts/hub/router/src/ibc/receive.rs +++ b/contracts/hub/router/src/ibc/receive.rs @@ -2,33 +2,41 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, IbcPacketReceiveMsg, - IbcReceiveResponse, MessageInfo, Order, Response, StdError, SubMsg, Uint128, WasmMsg, + IbcReceiveResponse, MessageInfo, Response, StdError, SubMsg, Uint128, WasmMsg, }; use euclid::{ chain::{ChainUid, CrossChainUser}, + deposit::DepositTokenResponse, error::ContractError, - events::{tx_event, TxType}, + events::{deregister_denom_event, register_denom_event, tx_event, TxType}, fee::Fee, - msgs::{self, router::ExecuteMsg, virtual_balance::ExecuteMint}, - pool::EscrowCreationResponse, - swap::WithdrawResponse, - token::{Pair, Token}, + msgs::{ + self, + router::{ExecuteMsg, TokenDenom}, + virtual_balance::{ExecuteMint, ExecuteMsg as VirtualBalanceMsg, ExecuteTransfer}, + }, + pool::{DeRegisterDenomResponse, RegisterDenomResponse}, + swap::{TransferResponse, WithdrawResponse}, + token::{PairWithDenomAndAmount, TokenWithDenom}, virtual_balance::BalanceKey, }; use euclid_ibc::{ ack::{make_ack_fail, AcknowledgementMsg}, - msg::{ChainIbcExecuteMsg, ChainIbcRemoveLiquidityExecuteMsg, ChainIbcSwapExecuteMsg}, + msg::{ + ChainIbcDepositTokenExecuteMsg, ChainIbcExecuteMsg, ChainIbcRemoveLiquidityExecuteMsg, + ChainIbcSwapExecuteMsg, ChainIbcTransferExecuteMsg, + }, }; use crate::{ query::validate_swap_pairs, reply::{ ADD_LIQUIDITY_REPLY_ID, IBC_RECEIVE_REPLY_ID, REMOVE_LIQUIDITY_REPLY_ID, SWAP_REPLY_ID, - VIRTUAL_BALANCE_MINT_REPLY_ID, VLP_INSTANTIATE_REPLY_ID, VLP_POOL_REGISTER_REPLY_ID, + VLP_INSTANTIATE_REPLY_ID, VLP_POOL_REGISTER_REPLY_ID, }, state::{ - CHAIN_UID_TO_CHAIN, CHANNEL_TO_CHAIN_UID, DEREGISTERED_CHAINS, ESCROW_BALANCES, - PENDING_REMOVE_LIQUIDITY, STATE, SWAP_ID_TO_MSG, VLPS, + CHAIN_UID_TO_CHAIN, CHANNEL_TO_CHAIN_UID, DEREGISTERED_CHAINS, ESCROW_BALANCES, FUNDS_INFO, + PENDING_REMOVE_LIQUIDITY, STATE, SWAP_ID_TO_MSG, TOKEN_DENOMS, VLPS, }, }; @@ -66,10 +74,15 @@ pub fn ibc_receive_internal_call( info: MessageInfo, msg: IbcPacketReceiveMsg, ) -> Result { + ensure!( + info.sender == env.contract.address, + ContractError::Unauthorized {} + ); // Get the chain data from current channel received let channel = msg.packet.dest.channel_id; let chain_uid = CHANNEL_TO_CHAIN_UID.load(deps.storage, channel)?; let chain = CHAIN_UID_TO_CHAIN.load(deps.storage, chain_uid.clone())?; + // Ensure source port is the registered factory ensure!( msg.packet.src.port_id == format!("wasm.{address}", address = chain.factory), @@ -86,26 +99,48 @@ pub fn reusable_internal_call( msg: ChainIbcExecuteMsg, chain_uid: ChainUid, ) -> Result { - let deregistered_chains = DEREGISTERED_CHAINS.load(deps.storage)?; + let locked = STATE.load(deps.storage)?.locked; + ensure!(!locked, ContractError::ContractLocked {}); + + let deregistered_chains = DEREGISTERED_CHAINS + .may_load(deps.storage)? + .unwrap_or_default(); ensure!( !deregistered_chains.contains(&chain_uid), ContractError::DeregisteredChain {} ); - let locked = STATE.load(deps.storage)?.locked; - ensure!(!locked, ContractError::ContractLocked {}); match msg { ChainIbcExecuteMsg::RequestPoolCreation { pair, sender, tx_id, + slippage_tolerance_bps, + } => { + ensure!( + sender.chain_uid == chain_uid, + ContractError::new("Chain UID mismatch") + ); + execute_request_pool_creation( + deps.branch(), + env, + sender, + pair, + tx_id, + slippage_tolerance_bps, + ) + } + ChainIbcExecuteMsg::RegisterDenom { + token, + sender, + tx_id, } => { ensure!( sender.chain_uid == chain_uid, ContractError::new("Chain UID mismatch") ); - execute_request_pool_creation(deps.branch(), env, sender, pair, tx_id) + execute_register_denom(deps.branch(), env, sender, token, tx_id) } - ChainIbcExecuteMsg::RequestEscrowCreation { + ChainIbcExecuteMsg::DeRegisterDenom { token, sender, tx_id, @@ -114,12 +149,10 @@ pub fn reusable_internal_call( sender.chain_uid == chain_uid, ContractError::new("Chain UID mismatch") ); - execute_request_escrow_creation(deps.branch(), env, sender, token, tx_id) + execute_deregister_denom(deps.branch(), env, sender, token, tx_id) } ChainIbcExecuteMsg::AddLiquidity { - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, + slippage_tolerance_bps, pair, tx_id, sender, @@ -129,16 +162,7 @@ pub fn reusable_internal_call( sender.chain_uid == chain_uid, ContractError::new("Chain UID mismatch") ); - ibc_execute_add_liquidity( - deps.branch(), - env, - sender, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, - pair, - tx_id, - ) + ibc_execute_add_liquidity(deps.branch(), sender, pair, slippage_tolerance_bps, tx_id) } ChainIbcExecuteMsg::RemoveLiquidity(msg) => { ensure!( @@ -180,6 +204,21 @@ pub fn reusable_internal_call( tx_id: msg.tx_id, }))?)) } + ChainIbcExecuteMsg::Transfer(msg) => { + ensure!( + msg.sender.chain_uid == chain_uid, + ContractError::new("Chain UID mismatch") + ); + ibc_execute_transfer_virtual_balance(deps.branch(), env, msg) + } + ChainIbcExecuteMsg::DepositToken(msg) => { + ensure!( + msg.sender.chain_uid == chain_uid, + ContractError::new("Chain UID mismatch") + ); + + ibc_execute_deposit_token(deps.branch(), env, msg) + } } } @@ -187,45 +226,87 @@ fn execute_request_pool_creation( deps: DepsMut, env: Env, sender: CrossChainUser, - pair: Pair, + pair_with_denom: PairWithDenomAndAmount, tx_id: String, + slippage_tolerance_bps: u64, ) -> Result { - pair.validate()?; let state = STATE.load(deps.storage)?; - let vlp = VLPS.may_load(deps.storage, pair.get_tupple())?; - - let register_msg = msgs::vlp::ExecuteMsg::RegisterPool { - sender: sender.clone(), - pair: pair.clone(), - tx_id: tx_id.clone(), - }; + let pair = pair_with_denom.get_pair()?; + pair.validate()?; - let response = Response::new() + let mut response = Response::new() .add_event(tx_event( &tx_id, &sender.to_sender_string(), TxType::PoolCreation, )) - .add_attribute("tx_id", tx_id) + .add_attribute("tx_id", tx_id.clone()) .add_attribute("method", "request_pool_creation"); - for token in pair.get_vec_token() { - let token_exists = - ESCROW_BALANCES.has(deps.storage, (token.clone(), sender.clone().chain_uid)); - let range = - ESCROW_BALANCES - .prefix(token) - .keys_raw(deps.storage, None, None, Order::Ascending); - // There are two cases - // token already exists on the sender chain - We can safely assume that this was validated already by factory so allow pool creation - // token not present in sender chain - This token should not have escrow on any other chain, i.e. This should be completely new token - ensure!( - token_exists || range.take(1).count() == 0, - ContractError::new("Cannot use already existing token without registering it first") - ) + let mut one_token_already_exists = false; + + for token in pair_with_denom.get_vec_token_info() { + let mut registered_denoms = TOKEN_DENOMS + .may_load(deps.storage, token.token.clone())? + .unwrap_or_default(); + + one_token_already_exists = one_token_already_exists || !registered_denoms.is_empty(); + + // If its a voucher, then we need to check if this token atleast exist on one of the chains + if token.token_type.is_voucher() { + ensure!( + !registered_denoms.is_empty(), + ContractError::new( + "Cannot create pool with voucher token that doesn't exist on any chain" + ) + ); + } else { + let token_registered_on_sender_chain = registered_denoms.iter().any(|denom| { + denom.chain_uid == sender.chain_uid && denom.token_type == token.token_type + }); + // If its not a voucher, then this token must be present on sender chain with sent denom or its completely new token + ensure!( + registered_denoms.is_empty() || token_registered_on_sender_chain, + ContractError::new( + format!( + "Token: {}:: sCannot use already existing denom without register first", + token.token + ) + .as_str() + ) + ); + // If its not a registered denom, lets register it now + if !token_registered_on_sender_chain { + registered_denoms.push(TokenDenom { + chain_uid: sender.chain_uid.clone(), + token_type: token.token_type.clone(), + }); + TOKEN_DENOMS.save(deps.storage, token.token.clone(), ®istered_denoms)?; + response = response.add_event(register_denom_event( + &token.token, + &sender.chain_uid.to_string(), + &token.token_type, + )); + } + } } + // Cannot create pool if both tokens are new + ensure!( + one_token_already_exists, + ContractError::new("Cannot create pool with two new tokens") + ); + + FUNDS_INFO.save(deps.storage, &(pair_with_denom, slippage_tolerance_bps))?; + + let register_msg = msgs::vlp::ExecuteMsg::RegisterPool { + sender: sender.clone(), + pair: pair.clone(), + tx_id, + }; + + let vlp = VLPS.may_load(deps.storage, pair.get_tupple())?; // If vlp is already there, send execute msg to it to register the pool, else create a new pool with register msg attached to instantiate msg if vlp.is_some() { let msg = WasmMsg::Execute { @@ -262,88 +343,114 @@ fn execute_request_pool_creation( funds: vec![], label: "VLP".to_string(), }; + Ok(response.add_submessage(SubMsg::reply_always(msg, VLP_INSTANTIATE_REPLY_ID))) } } -fn execute_request_escrow_creation( +fn execute_register_denom( deps: DepsMut, _env: Env, sender: CrossChainUser, - token: Token, + token: TokenWithDenom, tx_id: String, ) -> Result { - token.validate()?; + token.token.validate()?; + + let mut token_denoms = TOKEN_DENOMS + .load(deps.storage, token.token.clone()) + .unwrap_or_default(); + + let token_exists = token_denoms + .iter() + .any(|denom| denom.chain_uid == sender.chain_uid && denom.token_type == token.token_type); - let token_exists = ESCROW_BALANCES.has(deps.storage, (token.clone(), sender.clone().chain_uid)); ensure!(!token_exists, ContractError::TokenAlreadyExist {}); - ESCROW_BALANCES.save( - deps.storage, - (token.clone(), sender.clone().chain_uid), - &Uint128::zero(), - )?; + token_denoms.push(TokenDenom { + chain_uid: sender.chain_uid.clone(), + token_type: token.token_type.clone(), + }); + + TOKEN_DENOMS.save(deps.storage, token.token.clone(), &token_denoms)?; + + let ack: AcknowledgementMsg = + AcknowledgementMsg::Ok(RegisterDenomResponse {}); - let ack = AcknowledgementMsg::Ok(EscrowCreationResponse {}); Ok(Response::new() .add_event(tx_event( &tx_id, &sender.to_sender_string(), - TxType::EscrowCreation, + TxType::RegisterDenom, + )) + .add_event(register_denom_event( + &token.token, + &sender.chain_uid.to_string(), + &token.token_type, )) .add_attribute("tx_id", tx_id) - .add_attribute("method", "request_escrow_creation") + .add_attribute("method", "execute_register_denom") .set_data(to_json_binary(&ack)?)) } -fn ibc_execute_add_liquidity( +fn execute_deregister_denom( deps: DepsMut, _env: Env, sender: CrossChainUser, - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - slippage_tolerance: u64, - pair: Pair, + token: TokenWithDenom, tx_id: String, ) -> Result { - let vlp_address = VLPS.load(deps.storage, pair.get_tupple())?; - let pool_liquidity: euclid::msgs::vlp::GetLiquidityResponse = deps.querier.query_wasm_smart( - vlp_address.clone(), - &euclid::msgs::vlp::QueryMsg::Liquidity {}, - )?; + token.token.validate()?; + + let mut token_denoms = TOKEN_DENOMS + .load(deps.storage, token.token.clone()) + .unwrap_or_default(); + + let token_exists = token_denoms + .iter() + .any(|denom| denom.chain_uid == sender.chain_uid && denom.token_type == token.token_type); + + ensure!(token_exists, ContractError::AssetDoesNotExist {}); + + // Remove the denom from list + token_denoms.retain(|denom| { + denom.chain_uid != sender.chain_uid || denom.token_type != token.token_type + }); + + TOKEN_DENOMS.save(deps.storage, token.token.clone(), &token_denoms)?; + + let ack: AcknowledgementMsg = + AcknowledgementMsg::Ok(DeRegisterDenomResponse {}); + + Ok(Response::new() + .add_event(tx_event( + &tx_id, + &sender.to_sender_string(), + TxType::DeregisterDenom, + )) + .add_attribute("tx_id", tx_id) + .add_attribute("method", "execute_deregister_denom") + .add_event(deregister_denom_event( + &token.token, + &sender.chain_uid.to_string(), + &token.token_type, + )) + .set_data(to_json_binary(&ack)?)) +} + +pub fn ibc_execute_add_liquidity( + deps: DepsMut, + sender: CrossChainUser, + pair: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, + tx_id: String, +) -> Result { + let vlp_address = VLPS.load(deps.storage, pair.get_pair()?.get_tupple())?; let mut response = Response::new().add_event( tx_event(&tx_id, &sender.to_sender_string(), TxType::AddLiquidity) .add_attribute("tx_id", tx_id.clone()), ); - // Increase token 1 escrow balance - let token_1_escrow_key = ( - pool_liquidity.pair.token_1.clone(), - sender.chain_uid.clone(), - ); - let token_1_escrow_balance = ESCROW_BALANCES - .may_load(deps.storage, token_1_escrow_key.clone())? - .unwrap_or(Uint128::zero()); - - ESCROW_BALANCES.save( - deps.storage, - token_1_escrow_key, - &token_1_escrow_balance.checked_add(token_1_liquidity)?, - )?; - - // Increase token 2 escrow balance - let token_2_escrow_key = ( - pool_liquidity.pair.token_2.clone(), - sender.chain_uid.clone(), - ); - let token_2_escrow_balance = ESCROW_BALANCES - .may_load(deps.storage, token_2_escrow_key.clone())? - .unwrap_or(Uint128::zero()); - ESCROW_BALANCES.save( - deps.storage, - token_2_escrow_key, - &token_2_escrow_balance.checked_add(token_2_liquidity)?, - )?; let virtual_balance_address = STATE @@ -353,56 +460,71 @@ fn ibc_execute_add_liquidity( err: "virtual balance address doesn't exist".to_string(), })?; - let mint_virtual_balance_msg = euclid::msgs::virtual_balance::ExecuteMsg::Mint(ExecuteMint { - amount: token_1_liquidity, - balance_key: BalanceKey { - cross_chain_user: CrossChainUser { - address: vlp_address.to_string(), - chain_uid: ChainUid::vsl_chain_uid()?, - }, - token_id: pool_liquidity.pair.token_1.to_string(), - }, - }); - - let mint_virtual_balance_msg = WasmMsg::Execute { - contract_addr: virtual_balance_address.to_string(), - msg: to_json_binary(&mint_virtual_balance_msg)?, - funds: vec![], - }; - - response = response.add_submessage(SubMsg::reply_on_error( - mint_virtual_balance_msg, - VIRTUAL_BALANCE_MINT_REPLY_ID, - )); - - let mint_virtual_balance_msg = euclid::msgs::virtual_balance::ExecuteMsg::Mint(ExecuteMint { - amount: token_2_liquidity, - balance_key: BalanceKey { - cross_chain_user: CrossChainUser { - address: vlp_address.to_string(), - chain_uid: ChainUid::vsl_chain_uid()?, - }, - token_id: pool_liquidity.pair.token_2.to_string(), - }, - }); + for token in pair.get_vec_token_info() { + // If its a voucher token, then transfer it to the vlp contract + if token.token_type.is_voucher() { + // Transfer voucher token to the vlp contract + let transfer_voucher_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Transfer(ExecuteTransfer { + amount: token.amount, + token_id: token.token.to_string(), + from: sender.clone(), + to: CrossChainUser { + address: vlp_address.to_string(), + chain_uid: ChainUid::vsl_chain_uid()?, + }, + }); + + let transfer_voucher_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address.to_string(), + msg: to_json_binary(&transfer_voucher_msg)?, + funds: vec![], + }; - let mint_virtual_balance_msg = WasmMsg::Execute { - contract_addr: virtual_balance_address.to_string(), - msg: to_json_binary(&mint_virtual_balance_msg)?, - funds: vec![], - }; + // Should reject full execution if failed + response = response.add_message(transfer_voucher_msg); + } else { + // Increase Escrow balance + let token_escrow_key = (token.token.clone(), sender.chain_uid.clone()); + let token_escrow_balance = ESCROW_BALANCES + .may_load(deps.storage, token_escrow_key.clone())? + .unwrap_or(Uint128::zero()); + + ESCROW_BALANCES.save( + deps.storage, + token_escrow_key, + &token_escrow_balance.checked_add(token.amount)?, + )?; + + // Mint virtual balance for the token + let mint_virtual_balance_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Mint(ExecuteMint { + amount: token.amount, + balance_key: BalanceKey { + cross_chain_user: CrossChainUser { + address: vlp_address.to_string(), + chain_uid: ChainUid::vsl_chain_uid()?, + }, + token_id: token.token.to_string(), + }, + }); + + let mint_virtual_balance_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address.to_string(), + msg: to_json_binary(&mint_virtual_balance_msg)?, + funds: vec![], + }; - response = response.add_submessage(SubMsg::reply_on_error( - mint_virtual_balance_msg, - VIRTUAL_BALANCE_MINT_REPLY_ID, - )); + // Should reject full execution if failed + response = response.add_message(mint_virtual_balance_msg); + } + } let add_liquidity_msg = msgs::vlp::ExecuteMsg::AddLiquidity { - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, + liquidity: pair.get_pair_with_amount()?, sender, tx_id, + slippage_tolerance_bps, }; let msg = WasmMsg::Execute { @@ -468,7 +590,7 @@ fn ibc_execute_swap( })?; ensure!( - first_swap.token_in == msg.asset_in, + first_swap.token_in == msg.asset_in.token, ContractError::new("Asset IN doen't match router") ); @@ -518,44 +640,94 @@ fn ibc_execute_swap( err: "Swaps cannot be empty".to_string(), })?; - // Add token 1 in escrow balance - let token_escrow_key = (msg.asset_in.clone(), sender.chain_uid.clone()); - let token_1_escrow_balance = ESCROW_BALANCES - .may_load(deps.storage, token_escrow_key.clone())? - .unwrap_or(Uint128::zero()); + // Mint voucher token in escrow balance if it is not a voucher token + if msg.asset_in.token_type.is_voucher() { + let transfer_voucher_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Transfer(ExecuteTransfer { + amount: msg.amount_in, + token_id: msg.asset_in.token.to_string(), + from: sender.clone(), + to: CrossChainUser { + address: first_swap.vlp_address.clone(), + chain_uid: ChainUid::vsl_chain_uid()?, + }, + }); - ESCROW_BALANCES.save( - deps.storage, - token_escrow_key, - &token_1_escrow_balance.checked_add(msg.amount_in)?, - )?; + let transfer_voucher_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address.to_string(), + msg: to_json_binary(&transfer_voucher_msg)?, + funds: vec![], + }; - // Mint virtual balance for the first swap vlp so it can start processing tx - let mint_virtual_balance_msg = euclid::msgs::virtual_balance::ExecuteMsg::Mint(ExecuteMint { - amount: msg.amount_in, - balance_key: BalanceKey { - cross_chain_user: CrossChainUser { - address: first_swap.vlp_address.clone(), - chain_uid: ChainUid::vsl_chain_uid()?, - }, - token_id: msg.asset_in.to_string(), - }, - }); + // Should reject full execution if failed + response = response.add_message(transfer_voucher_msg); + } else { + let token_escrow_key = (msg.asset_in.token.clone(), sender.chain_uid.clone()); + let token_escrow_balance = ESCROW_BALANCES + .may_load(deps.storage, token_escrow_key.clone())? + .unwrap_or(Uint128::zero()); + + ESCROW_BALANCES.save( + deps.storage, + token_escrow_key, + &token_escrow_balance.checked_add(msg.amount_in)?, + )?; + + // Mint virtual balance for the first swap vlp so it can start processing tx + let mint_virtual_balance_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Mint(ExecuteMint { + amount: msg.amount_in, + balance_key: BalanceKey { + cross_chain_user: CrossChainUser { + address: first_swap.vlp_address.clone(), + chain_uid: ChainUid::vsl_chain_uid()?, + }, + token_id: msg.asset_in.token.to_string(), + }, + }); - let mint_virtual_balance_msg = WasmMsg::Execute { - contract_addr: virtual_balance_address.to_string(), - msg: to_json_binary(&mint_virtual_balance_msg)?, - funds: vec![], - }; + let mint_virtual_balance_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address.to_string(), + msg: to_json_binary(&mint_virtual_balance_msg)?, + funds: vec![], + }; - response = response.add_submessage(SubMsg::reply_always( - mint_virtual_balance_msg, - VIRTUAL_BALANCE_MINT_REPLY_ID, - )); + // Should reject full execution if failed + response = response.add_message(mint_virtual_balance_msg); + } + + if msg.asset_in.token_type.is_voucher() + && !msg.partner_fee_amount.is_zero() + && msg.partner_fee_recipient != sender + { + let transfer_voucher_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Transfer(ExecuteTransfer { + amount: msg.partner_fee_amount, + token_id: msg.asset_in.token.to_string(), + from: sender.clone(), + to: msg.partner_fee_recipient.clone(), + }); + + let transfer_voucher_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address.to_string(), + msg: to_json_binary(&transfer_voucher_msg)?, + funds: vec![], + }; + + // Should reject full execution if failed + response = response + .add_message(transfer_voucher_msg) + .add_attribute("partner_fee_transfer", "true") + .add_attribute( + "partner_fee_recipient", + msg.partner_fee_recipient.to_sender_string(), + ) + .add_attribute("partner_fee_amount", msg.partner_fee_amount.to_string()); + } let swap_msg = msgs::vlp::ExecuteMsg::Swap { sender: sender.clone(), - asset_in: msg.asset_in.clone(), + asset_in: msg.asset_in.token.clone(), amount_in: msg.amount_in, min_token_out: msg.min_amount_out, next_swaps: next_swaps.to_vec(), @@ -570,3 +742,109 @@ fn ibc_execute_swap( }; Ok(response.add_submessage(SubMsg::reply_always(msg, SWAP_REPLY_ID))) } + +fn ibc_execute_deposit_token( + deps: DepsMut, + _env: Env, + msg: ChainIbcDepositTokenExecuteMsg, +) -> Result { + let sender = msg.clone().sender; + + // Add token 1 in escrow balance + let token_escrow_key = (msg.asset_in.clone(), sender.chain_uid.clone()); + let token_escrow_balance = ESCROW_BALANCES + .may_load(deps.storage, token_escrow_key.clone())? + .unwrap_or(Uint128::zero()); + + ESCROW_BALANCES.save( + deps.storage, + token_escrow_key, + &token_escrow_balance.checked_add(msg.amount_in)?, + )?; + + let deposit_token_response = DepositTokenResponse { + amount: msg.amount_in, + token: msg.asset_in.clone(), + sender: msg.sender.clone(), + recipient: msg.recipient.clone(), + }; + let ack = AcknowledgementMsg::Ok(deposit_token_response.clone()); + + // Load state to get virtual balance address + let virtual_balance_address = STATE + .load(deps.storage)? + .virtual_balance_address + .map_or_else(|| Err(ContractError::EmptyVirtualBalanceAddress {}), Ok)?; + + // Send mint msg to virtual balance + let mint_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: virtual_balance_address.into_string(), + msg: to_json_binary(&VirtualBalanceMsg::Mint(ExecuteMint { + amount: msg.amount_in, + balance_key: BalanceKey { + cross_chain_user: msg.recipient, + token_id: msg.asset_in.to_string(), + }, + }))?, + funds: vec![], + }); + + Ok(Response::new() + .add_submessage(SubMsg::new(mint_msg)) + .add_attribute("action", "reply_deposit_token") + .add_attribute( + "deposit_token_response", + format!("{deposit_token_response:?}"), + ) + .add_event( + tx_event( + &msg.tx_id, + &msg.sender.to_sender_string(), + TxType::DepositToken, + ) + .add_attribute("tx_id", msg.tx_id.clone()), + ) + .set_data(to_json_binary(&ack)?)) +} + +fn ibc_execute_transfer_virtual_balance( + deps: DepsMut, + _env: Env, + msg: ChainIbcTransferExecuteMsg, +) -> Result { + let virtual_balance_address = STATE + .load(deps.storage)? + .virtual_balance_address + .map_or(Err(ContractError::EmptyVirtualBalanceAddress {}), Ok)? + .into_string(); + + let transfer_voucher_msg = + euclid::msgs::virtual_balance::ExecuteMsg::Transfer(ExecuteTransfer { + amount: msg.amount, + token_id: msg.token.to_string(), + from: msg.clone().sender, + to: msg.recipient_address, + }); + + let transfer_voucher_msg = WasmMsg::Execute { + contract_addr: virtual_balance_address, + msg: to_json_binary(&transfer_voucher_msg)?, + funds: vec![], + }; + + Ok(Response::default() + .add_message(transfer_voucher_msg) + .add_attribute("action", "transfer_virtual_balance") + .add_event( + tx_event( + &msg.tx_id, + &msg.sender.to_sender_string(), + TxType::TransferVirtualBalance, + ) + .add_attribute("tx_id", msg.tx_id.clone()), + ) + .set_data(to_json_binary(&AcknowledgementMsg::Ok(TransferResponse { + token: msg.token, + tx_id: msg.tx_id, + }))?)) +} diff --git a/contracts/hub/router/src/interface.rs b/contracts/hub/router/src/interface.rs new file mode 100644 index 00000000..2d35dc5c --- /dev/null +++ b/contracts/hub/router/src/interface.rs @@ -0,0 +1,25 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::router::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "router_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)] +pub struct RouterContract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for RouterContract { + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(crate::contract::reply) + .with_ibc( + crate::ibc::channel::ibc_channel_open, + crate::ibc::channel::ibc_channel_connect, + crate::ibc::channel::ibc_channel_close, + crate::ibc::receive::ibc_packet_receive, + crate::ibc::ack_and_timeout::ibc_packet_ack, + crate::ibc::ack_and_timeout::ibc_packet_timeout, + ), + ) + } +} diff --git a/contracts/hub/router/src/lib.rs b/contracts/hub/router/src/lib.rs index 97af41c4..0294a98c 100644 --- a/contracts/hub/router/src/lib.rs +++ b/contracts/hub/router/src/lib.rs @@ -5,7 +5,13 @@ pub mod migrate; pub mod query; pub mod reply; pub mod state; +#[cfg(test)] mod tests; #[cfg(not(target_arch = "wasm32"))] pub mod mock; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::RouterContract; diff --git a/contracts/hub/router/src/migrate.rs b/contracts/hub/router/src/migrate.rs index 6dea066b..6d29febf 100644 --- a/contracts/hub/router/src/migrate.rs +++ b/contracts/hub/router/src/migrate.rs @@ -1,9 +1,39 @@ use cosmwasm_std::{entry_point, DepsMut, Env, Response}; -use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; +use cw2::CONTRACT; +use euclid::{error::ContractError, msgs::router::MigrateMsg}; + +use crate::state::TOKEN_DENOMS; /// This is the migrate entry point for the contract. -/// Currently, it does not perform any migration logic and simply returns an empty response. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Ok(Response::default()) +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + let response = migrate_v0_2_0_to_v0_2_1(deps, env, msg)?; + Ok(response) +} + +// Migrate v0.2.0 to 0.2.1 with token denoms +fn migrate_v0_2_0_to_v0_2_1( + deps: DepsMut, + _env: Env, + msg: MigrateMsg, +) -> Result { + let contract_version = CONTRACT.load(deps.storage)?; + if contract_version.version != "0.2.0" { + return Ok(Response::default()); + } + + let denoms_iter = msg.denoms.iter(); + for (token, denom) in denoms_iter { + let mut token_denoms = TOKEN_DENOMS + .may_load(deps.storage, token.clone())? + .unwrap_or_default(); + token_denoms.push(denom.clone()); + TOKEN_DENOMS + .save(deps.storage, token.clone(), &token_denoms) + .unwrap(); + } + + Ok(Response::default() + .add_attribute("action", "migrate") + .add_attribute("migrated_tokens", "true")) } diff --git a/contracts/hub/router/src/query.rs b/contracts/hub/router/src/query.rs index baeda905..f62517d9 100644 --- a/contracts/hub/router/src/query.rs +++ b/contracts/hub/router/src/query.rs @@ -1,19 +1,20 @@ use cosmwasm_std::{ensure, to_json_binary, Binary, Deps, Order, Uint128}; use cw_storage_plus::{Bound, PrefixBound}; use euclid::{ - chain::{ChainUid, CrossChainUserWithLimit}, + chain::{ChainUid, CrossChainUser, CrossChainUserWithLimit, Limit}, error::ContractError, msgs::router::{ - AllChainResponse, AllTokensResponse, AllVlpResponse, ChainResponse, QuerySimulateSwap, - SimulateEscrowReleaseResponse, SimulateSwapResponse, StateResponse, - TokenEscrowChainResponse, TokenEscrowsResponse, TokenResponse, VlpResponse, + AllChainResponse, AllEscrowsResponse, AllTokensResponse, AllVlpResponse, ChainResponse, + EscrowResponse, QuerySimulateSwap, SimulateEscrowReleaseResponse, SimulateSwapResponse, + StateResponse, TokenDenomsResponse, TokenEscrowChainResponse, TokenEscrowsResponse, + VlpResponse, }, swap::{NextSwapPair, NextSwapVlp}, token::{Pair, Token}, - utils::Pagination, + utils::pagination::{Pagination, DEFAULT_PAGINATION_LIMIT, DEFAULT_PAGINATION_SKIP}, }; -use crate::state::{CHAIN_UID_TO_CHAIN, ESCROW_BALANCES, STATE, VLPS}; +use crate::state::{CHAIN_UID_TO_CHAIN, ESCROW_BALANCES, STATE, TOKEN_DENOMS, VLPS}; pub fn query_state(deps: Deps) -> Result { let state = STATE.load(deps.storage)?; @@ -161,8 +162,36 @@ pub fn query_simulate_escrow_release( remaining_withdraw_amount }; - let release_amount = release_amount.min(cross_chain_address.limit.unwrap_or(Uint128::MAX)); - + match cross_chain_address.limit { + Some(Limit::LessThanOrEqual(limit)) => { + ensure!( + release_amount.le(&limit), + ContractError::LimitExceeded { + limit, + amount: release_amount + } + ); + } + Some(Limit::Equal(limit)) => { + ensure!( + release_amount.eq(&limit), + ContractError::AmountMismatch { + expected: limit, + received: release_amount + } + ); + } + Some(Limit::GreaterThanOrEqual(limit)) => { + ensure!( + release_amount.ge(&limit), + ContractError::InsufficientAmount { + min_amount: limit, + amount: release_amount + } + ); + } + _ => {} + } if release_amount.is_zero() { continue; } @@ -225,7 +254,7 @@ pub fn query_token_escrows( Ok(to_json_binary(&TokenEscrowsResponse { chains: chains? })?) } -pub fn query_all_tokens( +pub fn query_all_escrows( deps: Deps, pagination: Pagination, ) -> Result { @@ -238,18 +267,69 @@ pub fn query_all_tokens( let start = start.map(PrefixBound::inclusive); let end = end.map(PrefixBound::exclusive); - let tokens: Result<_, ContractError> = ESCROW_BALANCES + let escrows: Result<_, ContractError> = ESCROW_BALANCES .prefix_range(deps.storage, start, end, Order::Ascending) - .skip(skip.unwrap_or(0) as usize) - .take(limit.unwrap_or(10) as usize) + .skip(skip.unwrap_or(DEFAULT_PAGINATION_SKIP) as usize) + .take(limit.unwrap_or(DEFAULT_PAGINATION_LIMIT) as usize) .map(|v| { let v = v?; - Ok(TokenResponse { + Ok(EscrowResponse { token: v.0 .0, chain_uid: v.0 .1, + balance: v.1, }) }) .collect(); - Ok(to_json_binary(&AllTokensResponse { tokens: tokens? })?) + Ok(to_json_binary(&AllEscrowsResponse { escrows: escrows? })?) +} + +pub fn query_all_tokens( + deps: Deps, + pagination: Pagination, +) -> Result { + let Pagination { + min: start, + max: end, + skip, + limit, + } = pagination; + + let start = start.map(Bound::inclusive); + let end = end.map(Bound::exclusive); + let tokens = TOKEN_DENOMS + .keys(deps.storage, start, end, Order::Ascending) + .skip(skip.unwrap_or(DEFAULT_PAGINATION_SKIP) as usize) + .take(limit.unwrap_or(DEFAULT_PAGINATION_LIMIT) as usize) + .flatten() + .collect(); + + Ok(to_json_binary(&AllTokensResponse { tokens })?) +} + +pub fn query_token_denoms(deps: Deps, token: Token) -> Result { + let denoms = TOKEN_DENOMS.load(deps.storage, token)?; + Ok(to_json_binary(&TokenDenomsResponse { denoms })?) +} + +pub fn verify_cross_chain_addresses( + deps: Deps, + users: Vec, +) -> Result<(), ContractError> { + for user in users.iter() { + ensure!( + !user.address.is_empty(), + ContractError::Generic { + err: "Address cannot be empty".to_string() + } + ); + let chain_uid = user.chain_uid.clone(); + ensure!( + CHAIN_UID_TO_CHAIN.has(deps.storage, chain_uid.clone()), + ContractError::Generic { + err: "Chain UID not registered".to_string() + } + ); + } + Ok(()) } diff --git a/contracts/hub/router/src/reply.rs b/contracts/hub/router/src/reply.rs index 690f16bb..7f9a9249 100644 --- a/contracts/hub/router/src/reply.rs +++ b/contracts/hub/router/src/reply.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, Reply, Response, SubMsgResult, - WasmMsg, + ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, Reply, Response, SubMsg, + SubMsgResult, WasmMsg, }; use cw_utils::{ parse_execute_response_data, parse_reply_execute_data, parse_reply_instantiate_data, @@ -22,8 +22,8 @@ use euclid_ibc::{ }; use crate::{ - ibc, - state::{PENDING_REMOVE_LIQUIDITY, STATE, SWAP_ID_TO_MSG, VLPS}, + ibc::{self, receive::ibc_execute_add_liquidity}, + state::{FUNDS_INFO, PENDING_REMOVE_LIQUIDITY, STATE, SWAP_ID_TO_MSG, TOKEN_VLPS, VLPS}, }; pub const VLP_INSTANTIATE_REPLY_ID: u64 = 1; @@ -35,10 +35,6 @@ pub const SWAP_REPLY_ID: u64 = 5; pub const VIRTUAL_BALANCE_INSTANTIATE_REPLY_ID: u64 = 6; pub const ESCROW_BALANCE_INSTANTIATE_REPLY_ID: u64 = 7; -pub const VIRTUAL_BALANCE_MINT_REPLY_ID: u64 = 8; -pub const VIRTUAL_BALANCE_BURN_REPLY_ID: u64 = 9; -pub const VIRTUAL_BALANCE_TRANSFER_REPLY_ID: u64 = 10; - pub const IBC_RECEIVE_REPLY_ID: u64 = 11; pub const IBC_ACK_AND_TIMEOUT_REPLY_ID: u64 = 12; @@ -57,34 +53,41 @@ pub fn on_vlp_instantiate_reply(deps: DepsMut, msg: Reply) -> Result( + instantiate_data.data.clone().unwrap_or_default(), + )?; + let (funds, slippage_tolerance_bps) = FUNDS_INFO + .load(deps.storage) + .map_err(|_| ContractError::InsufficientFunds {})?; - let pool_creation_response = - from_json::(instantiate_data.data.unwrap_or_default()); - - // This is probably IBC Message so send ok Ack as data - if pool_creation_response.is_ok() { - let ack = AcknowledgementMsg::Ok(pool_creation_response?); - - Ok(Response::new() - .add_attribute("action", "reply_vlp_instantiate") - .add_attribute("vlp", vlp_address) - .add_attribute("action", "reply_pool_register") - .set_data(to_json_binary(&ack)?)) - } else { - Ok(Response::new() - .add_attribute("action", "reply_vlp_instantiate") - .add_attribute("vlp", vlp_address)) - } + let response = ibc_execute_add_liquidity( + deps, + pool_creation_response.sender.clone(), + funds, + slippage_tolerance_bps, + pool_creation_response.tx_id.clone(), + )?; + + Ok(response + .add_attribute("action", "reply_vlp_instantiate") + .add_attribute("vlp", vlp_address)) } } } -pub fn on_pool_register_reply(_deps: DepsMut, msg: Reply) -> Result { +pub fn on_pool_register_reply(deps: DepsMut, msg: Reply) -> Result { match msg.result.clone() { SubMsgResult::Err(err) => Err(ContractError::Generic { err }), SubMsgResult::Ok(..) => { @@ -94,12 +97,23 @@ pub fn on_pool_register_reply(_deps: DepsMut, msg: Reply) -> Result Result Result { +pub fn on_add_liquidity_reply(deps: DepsMut, msg: Reply) -> Result { match msg.result.clone() { SubMsgResult::Err(err) => Err(ContractError::Generic { err }), SubMsgResult::Ok(..) => { @@ -118,12 +132,30 @@ pub fn on_add_liquidity_reply(_deps: DepsMut, msg: Reply) -> Result { + let pool_response = PoolCreationResponse { + mint_lp_tokens: liquidity_response.mint_lp_tokens, + vlp_contract: liquidity_response.vlp_address.clone(), + tx_id: liquidity_response.tx_id.clone(), + sender: liquidity_response.sender.clone(), + }; + FUNDS_INFO.remove(deps.storage); + + let ack = AcknowledgementMsg::Ok(pool_response); + res = res.set_data(to_json_binary(&ack)?); + } + None => { + let ack = AcknowledgementMsg::Ok(liquidity_response.clone()); + res = res.set_data(to_json_binary(&ack)?); + } + } - Ok(Response::new() + Ok(res .add_attribute("action", "reply_add_liquidity") - .add_attribute("liquidity", format!("{liquidity_response:?}")) - .set_data(to_json_binary(&ack)?)) + .add_attribute("liquidity", format!("{liquidity_response:?}"))) } } } @@ -136,6 +168,8 @@ pub fn on_remove_liquidity_reply( match msg.result.clone() { SubMsgResult::Err(err) => Err(ContractError::Generic { err }), SubMsgResult::Ok(..) => { + let mut response = Response::new().add_attribute("action", "reply_remove_liquidity"); + let execute_data = parse_reply_execute_data(msg).map_err(|res| ContractError::Generic { err: res.to_string(), @@ -151,51 +185,41 @@ pub fn on_remove_liquidity_reply( let remove_liquidity_tx = req_key.load(deps.storage)?; req_key.remove(deps.storage); - let token_1_escrow_release_msg = - euclid::msgs::router::ExecuteMsg::ReleaseEscrowInternal { - sender: remove_liquidity_tx.sender.clone(), - token: remove_liquidity_tx.pair.token_1.clone(), - amount: Some(vlp_liquidity_response.token_1_liquidity_released), - cross_chain_addresses: remove_liquidity_tx.cross_chain_addresses.clone(), - timeout: None, - tx_id: vlp_liquidity_response.tx_id.clone(), - }; - - let token_1_escrow_release_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_json_binary(&token_1_escrow_release_msg)?, - funds: vec![], - }); - - let token_2_escrow_release_msg = - euclid::msgs::router::ExecuteMsg::ReleaseEscrowInternal { - sender: remove_liquidity_tx.sender, - token: remove_liquidity_tx.pair.token_2, - amount: Some(vlp_liquidity_response.token_2_liquidity_released), - cross_chain_addresses: remove_liquidity_tx.cross_chain_addresses, - timeout: None, - tx_id: vlp_liquidity_response.tx_id, - }; - let token_2_escrow_release_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_json_binary(&token_2_escrow_release_msg)?, - funds: vec![], - }); + for token in vlp_liquidity_response.liquidity_released.get_vec_token() { + let token_escrow_release_msg = + euclid::msgs::router::ExecuteMsg::ReleaseEscrowInternal { + sender: remove_liquidity_tx.sender.clone(), + token: token.token.clone(), + amount: Some(token.amount), + cross_chain_addresses: remove_liquidity_tx.cross_chain_addresses.clone(), + timeout: None, + tx_id: vlp_liquidity_response.tx_id.clone(), + }; + + let token_escrow_release_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_json_binary(&token_escrow_release_msg)?, + funds: vec![], + }); + response = response + .add_message(token_escrow_release_msg) + .add_attribute( + format!("token_removed_{}", token.token), + token.amount.to_string(), + ); + } let liquidity_response = RemoveLiquidityResponse { - token_1_liquidity: vlp_liquidity_response.token_1_liquidity_released, - token_2_liquidity: vlp_liquidity_response.token_2_liquidity_released, burn_lp_tokens: vlp_liquidity_response.burn_lp_tokens, vlp_address: vlp_liquidity_response.vlp_address, + liquidity_removed: vlp_liquidity_response.liquidity_released, }; let ack = AcknowledgementMsg::Ok(liquidity_response.clone()); - Ok(Response::new() - .add_attribute("action", "reply_remove_liquidity") + Ok(response .add_attribute("liquidity", format!("{liquidity_response:?}")) - .add_message(token_1_escrow_release_msg) - .add_message(token_2_escrow_release_msg) + .add_attribute("lp_burned", liquidity_response.burn_lp_tokens.to_string()) .set_data(to_json_binary(&ack)?)) } } @@ -237,7 +261,6 @@ pub fn on_swap_reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result Result { - match msg.result.clone() { - SubMsgResult::Err(err) => Err(ContractError::Generic { err }), - SubMsgResult::Ok(..) => Ok(Response::new() - .add_attribute("action", "reply_mint_virtual_balance") - .add_attribute("mint_success", "true")), - } -} - -pub fn on_virtual_balance_burn_reply( - _deps: DepsMut, - msg: Reply, -) -> Result { - match msg.result.clone() { - SubMsgResult::Err(err) => Err(ContractError::Generic { err }), - SubMsgResult::Ok(..) => Ok(Response::new() - .add_attribute("action", "reply_burn_virtual_balance") - .add_attribute("burn_success", "true")), - } -} - -pub fn on_virtual_balance_transfer_reply( - _deps: DepsMut, - msg: Reply, -) -> Result { - match msg.result.clone() { - SubMsgResult::Err(err) => Err(ContractError::Generic { err }), - SubMsgResult::Ok(..) => Ok(Response::new() - .add_attribute("action", "reply_transfer_virtual_balance") - .add_attribute("transfer_success", "true")), - } -} - pub fn on_ibc_ack_and_timeout_reply(_deps: DepsMut, msg: Reply) -> Result { match msg.result.clone() { SubMsgResult::Err(err) => Ok(Response::new() diff --git a/contracts/hub/router/src/state.rs b/contracts/hub/router/src/state.rs index d9e9b05b..cd30f116 100644 --- a/contracts/hub/router/src/state.rs +++ b/contracts/hub/router/src/state.rs @@ -3,7 +3,8 @@ use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; use euclid::{ chain::{Chain, ChainUid}, - token::Token, + msgs::router::TokenDenom, + token::{PairWithDenomAndAmount, Token}, }; use euclid_ibc::msg::{ChainIbcRemoveLiquidityExecuteMsg, ChainIbcSwapExecuteMsg}; @@ -22,6 +23,12 @@ pub const STATE: Item = Item::new("state"); // Convert it to multi index map? pub const VLPS: Map<(Token, Token), String> = Map::new("vlps"); +// Store all tokens in a map for easy access +pub const TOKEN_VLPS: Map> = Map::new("token_vlps"); + +// Store all tokens in a map for easy access +pub const TOKEN_DENOMS: Map> = Map::new("token_denoms"); + // Token escrow balance on each chain pub const ESCROW_BALANCES: Map<(Token, ChainUid), Uint128> = Map::new("escrow_balances"); @@ -38,3 +45,5 @@ pub const PENDING_REMOVE_LIQUIDITY: Map< (ChainUid, String, String), ChainIbcRemoveLiquidityExecuteMsg, > = Map::new("pending_remove_liquidity"); + +pub const FUNDS_INFO: Item<(PairWithDenomAndAmount, u64)> = Item::new("funds_info"); diff --git a/contracts/hub/router/src/tests.rs b/contracts/hub/router/src/tests.rs index b31d41af..9d0ff0d9 100644 --- a/contracts/hub/router/src/tests.rs +++ b/contracts/hub/router/src/tests.rs @@ -1,9 +1,11 @@ +#[allow(clippy::module_inception)] #[cfg(test)] mod tests { + #[cfg(test)] use crate::contract::{execute, instantiate}; use crate::state::{State, CHAIN_UID_TO_CHAIN, STATE}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{from_json, CosmosMsg, DepsMut, IbcMsg, MessageInfo, Response}; + use cosmwasm_std::{from_json, Addr, CosmosMsg, DepsMut, IbcMsg, MessageInfo, Response}; use euclid::chain::{Chain, ChainUid, IbcChain}; use euclid::error::ContractError; use euclid::msgs::router::{ExecuteMsg, InstantiateMsg, RegisterFactoryChainNative}; @@ -39,57 +41,6 @@ mod tests { assert_eq!(expected_state, state) } - #[test] - fn test_execute_update_vlp_code_id() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info("creator", &[]); - - // Instantiate the contract first - let msg = InstantiateMsg { - vlp_code_id: 1, - virtual_balance_code_id: 2, - }; - instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); - - let test_cases = vec![ - TestExecuteMsg { - name: "Update VLP Code ID by admin", - msg: ExecuteMsg::UpdateVLPCodeId { new_vlp_code_id: 2 }, - expected_error: None, - }, - TestExecuteMsg { - name: "Update VLP Code ID by non-admin", - msg: ExecuteMsg::UpdateVLPCodeId { new_vlp_code_id: 3 }, - expected_error: Some(ContractError::Unauthorized {}), - }, - ]; - - for test in test_cases { - let res = execute( - deps.as_mut(), - env.clone(), - if test.name.contains("non-admin") { - mock_info("non-admin", &[]) - } else { - info.clone() - }, - test.msg.clone(), - ); - match test.expected_error { - Some(err) => assert_eq!(res.unwrap_err(), err, "{}", test.name), - None => { - assert!(res.is_ok(), "{}", test.name); - - // Verify the state was updated - let state: State = STATE.load(&deps.storage).unwrap(); - if let ExecuteMsg::UpdateVLPCodeId { new_vlp_code_id } = test.msg { - assert_eq!(state.vlp_code_id, new_vlp_code_id); - } - } - } - } - } #[test] fn test_execute_register_factory() { @@ -197,7 +148,7 @@ mod tests { execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let state = STATE.load(deps.as_ref().storage).unwrap(); - assert_eq!(state.locked, true); + assert!(state.locked); // Try to call a function while locked let msg = ExecuteMsg::UpdateFactoryChannel { @@ -212,7 +163,7 @@ mod tests { let msg = ExecuteMsg::UpdateLock {}; execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let state = STATE.load(deps.as_ref().storage).unwrap(); - assert_eq!(state.locked, false); + assert!(!state.locked); // Now try calling the earlier message let msg = ExecuteMsg::UpdateFactoryChannel { @@ -235,4 +186,35 @@ mod tests { .unwrap(); execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); } + + #[test] + fn test_update_router_state() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("owner", &[]); + init(deps.as_mut(), info); + + // Unauthorized + let msg = ExecuteMsg::UpdateRouterState { + admin: Some("new_admin".to_string()), + vlp_code_id: Some(1), + virtual_balance_address: Some(Addr::unchecked("new_virtual_balance_address")), + locked: Some(true), + }; + let info = mock_info("not_owner", &[]); + let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized {}); + + // Works + let info = mock_info("owner", &[]); + execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + let state = STATE.load(deps.as_ref().storage).unwrap(); + assert_eq!(state.admin, "new_admin".to_string()); + assert_eq!(state.vlp_code_id, 1); + assert_eq!( + state.virtual_balance_address, + Some(Addr::unchecked("new_virtual_balance_address")) + ); + assert!(state.locked); + } } diff --git a/contracts/hub/virtual_balance/Cargo.toml b/contracts/hub/virtual_balance/Cargo.toml index ce2572db..2a85621e 100644 --- a/contracts/hub/virtual_balance/Cargo.toml +++ b/contracts/hub/virtual_balance/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "virtual_balance" -version = "0.1.0" -authors = ["Anshudhar Kumar Singh "] +version = "0.2.1" +authors = [ + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -47,6 +50,7 @@ schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } +cw-orch = "=0.24.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/hub/virtual_balance/src/contract.rs b/contracts/hub/virtual_balance/src/contract.rs index 98b70822..24f1502c 100644 --- a/contracts/hub/virtual_balance/src/contract.rs +++ b/contracts/hub/virtual_balance/src/contract.rs @@ -3,8 +3,9 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; +use crate::execute::{execute_burn, execute_mint, execute_transfer, execute_update_state}; +use crate::query::{query_balance, query_state, query_user_balances}; use crate::state::STATE; -use crate::{execute, query}; use euclid::error::ContractError; use euclid::msgs::virtual_balance::{ExecuteMsg, InstantiateMsg, QueryMsg, State}; @@ -42,19 +43,22 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Mint(msg) => execute::execute_mint(deps, info, msg), - ExecuteMsg::Burn(msg) => execute::execute_burn(deps, info, msg), - ExecuteMsg::Transfer(msg) => execute::execute_transfer(deps, info, msg), + ExecuteMsg::Mint(msg) => execute_mint(deps, info, msg), + ExecuteMsg::Burn(msg) => execute_burn(deps, info, msg), + ExecuteMsg::Transfer(msg) => execute_transfer(deps, info, msg), + ExecuteMsg::UpdateState { router, admin } => { + execute_update_state(deps, info, router, admin) + } } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { - QueryMsg::GetState {} => query::query_state(deps), - QueryMsg::GetBalance { balance_key } => query::query_balance(deps, balance_key), + QueryMsg::GetState {} => query_state(deps), + QueryMsg::GetBalance { balance_key } => query_balance(deps, balance_key), QueryMsg::GetUserBalances { user } => { - query::query_user_balances(deps, user.chain_uid, user.address) + query_user_balances(deps, user.chain_uid, user.address) } } } diff --git a/contracts/hub/virtual_balance/src/execute.rs b/contracts/hub/virtual_balance/src/execute.rs index df57e4b9..4186407c 100644 --- a/contracts/hub/virtual_balance/src/execute.rs +++ b/contracts/hub/virtual_balance/src/execute.rs @@ -1,8 +1,8 @@ -use cosmwasm_std::{ensure, DepsMut, MessageInfo, Response, Uint128}; +use cosmwasm_std::{ensure, Addr, DepsMut, MessageInfo, Response, Uint128}; use euclid::{ chain::ChainUid, error::ContractError, - msgs::virtual_balance::{ExecuteBurn, ExecuteMint, ExecuteTransfer}, + msgs::virtual_balance::{ExecuteBurn, ExecuteMint, ExecuteTransfer, State}, virtual_balance::BalanceKey, }; @@ -115,9 +115,10 @@ pub fn execute_transfer( // Added here just for additional safety ensure!( sender_old_balance.ge(&msg.amount), - ContractError::Generic { - err: "Not Enough Funds".to_string() - } + ContractError::new(&format!( + "Not Enough Funds: {} < {}", + sender_old_balance, msg.amount + )) ); let sender_new_balance = sender_old_balance.checked_sub(msg.amount)?; @@ -139,3 +140,45 @@ pub fn execute_transfer( .add_attribute("to", format!("{receiver_balance_key:?}")) .add_attribute("burn_token_id", msg.token_id)) } + +pub fn execute_update_state( + deps: DepsMut, + info: MessageInfo, + router: Option, + admin: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + ensure!(info.sender == state.admin, ContractError::Unauthorized {}); + + let verified_router = if let Some(ref router) = router { + deps.api.addr_validate(router.as_str())?; + router.clone() + } else { + state.router + }; + + let verified_admin = if let Some(ref admin) = admin { + deps.api.addr_validate(admin.as_str())?; + admin.clone() + } else { + state.admin + }; + + let new_state = State { + router: verified_router, + admin: verified_admin, + }; + + STATE.save(deps.storage, &new_state)?; + + Ok(Response::new() + .add_attribute("action", "execute_update_state") + .add_attribute( + "router", + router.map_or_else(|| "unchanged".to_string(), |router| router.to_string()), + ) + .add_attribute( + "admin", + admin.map_or_else(|| "unchanged".to_string(), |admin| admin.to_string()), + )) +} diff --git a/contracts/hub/virtual_balance/src/interface.rs b/contracts/hub/virtual_balance/src/interface.rs new file mode 100644 index 00000000..936e6dbb --- /dev/null +++ b/contracts/hub/virtual_balance/src/interface.rs @@ -0,0 +1,14 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::virtual_balance::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "virtual_balance_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)] +pub struct VirtualBalanceContract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for VirtualBalanceContract { + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/contracts/hub/virtual_balance/src/lib.rs b/contracts/hub/virtual_balance/src/lib.rs index e8f9d06e..b07dde3f 100644 --- a/contracts/hub/virtual_balance/src/lib.rs +++ b/contracts/hub/virtual_balance/src/lib.rs @@ -8,3 +8,8 @@ pub mod state; mod tests; pub mod mock; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::VirtualBalanceContract; diff --git a/contracts/hub/virtual_balance/src/migrate.rs b/contracts/hub/virtual_balance/src/migrate.rs index 6dea066b..f39345b3 100644 --- a/contracts/hub/virtual_balance/src/migrate.rs +++ b/contracts/hub/virtual_balance/src/migrate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{entry_point, DepsMut, Env, Response}; -use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; +use euclid::{error::ContractError, msgs::virtual_balance::MigrateMsg}; /// This is the migrate entry point for the contract. /// Currently, it does not perform any migration logic and simply returns an empty response. diff --git a/contracts/hub/virtual_balance/src/tests.rs b/contracts/hub/virtual_balance/src/tests.rs index f5a4b166..f12d3dca 100644 --- a/contracts/hub/virtual_balance/src/tests.rs +++ b/contracts/hub/virtual_balance/src/tests.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] #[cfg(test)] mod tests { diff --git a/contracts/hub/vlp/Cargo.toml b/contracts/hub/vlp/Cargo.toml index 63778a84..8e513860 100644 --- a/contracts/hub/vlp/Cargo.toml +++ b/contracts/hub/vlp/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "vlp" -version = "0.1.0" -authors = ["gachouchani1999 "] +version = "0.2.1" +authors = [ + "gachouchani1999 ", + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -48,6 +52,7 @@ schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } +cw-orch = "=0.24.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/hub/vlp/src/contract.rs b/contracts/hub/vlp/src/contract.rs index 4fbba6bb..885cdba4 100644 --- a/contracts/hub/vlp/src/contract.rs +++ b/contracts/hub/vlp/src/contract.rs @@ -6,6 +6,9 @@ use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, Uin use cw2::set_contract_version; use euclid::fee::{DenomFees, TotalFees}; +use crate::execute::{ + add_liquidity, execute_swap, register_pool, remove_liquidity, update_fee, update_state, +}; use crate::reply::{NEXT_SWAP_REPLY_ID, VIRTUAL_BALANCE_TRANSFER_REPLY_ID}; use crate::state::{State, BALANCES, STATE}; use crate::{execute, reply}; @@ -83,33 +86,31 @@ pub fn execute( sender, pair, tx_id, - } => execute::register_pool(deps, env, info, sender, pair, tx_id), + } => register_pool(deps, env, info, sender, pair, tx_id), ExecuteMsg::UpdateFee { lp_fee_bps, euclid_fee_bps, recipient, - } => execute::update_fee(deps, info, lp_fee_bps, euclid_fee_bps, recipient), + } => update_fee(deps, info, lp_fee_bps, euclid_fee_bps, recipient), ExecuteMsg::AddLiquidity { sender, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, tx_id, - } => execute::add_liquidity( + slippage_tolerance_bps, + liquidity, + } => add_liquidity( deps, env, info, sender, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, + liquidity, + slippage_tolerance_bps, tx_id, ), ExecuteMsg::RemoveLiquidity { sender, lp_allocation, tx_id, - } => execute::remove_liquidity(deps, env, info, sender, lp_allocation, tx_id), + } => remove_liquidity(deps, env, info, sender, lp_allocation, tx_id), ExecuteMsg::Swap { sender, asset_in, @@ -118,7 +119,7 @@ pub fn execute( tx_id, next_swaps, test_fail, - } => execute::execute_swap( + } => execute_swap( deps, env, sender, @@ -129,6 +130,21 @@ pub fn execute( next_swaps, test_fail, ), + ExecuteMsg::UpdateState { + router, + virtual_balance, + fee, + last_updated, + admin, + } => update_state( + deps, + info, + router, + virtual_balance, + fee, + last_updated, + admin, + ), } } diff --git a/contracts/hub/vlp/src/execute.rs b/contracts/hub/vlp/src/execute.rs index ac21c27a..8e48f5b1 100644 --- a/contracts/hub/vlp/src/execute.rs +++ b/contracts/hub/vlp/src/execute.rs @@ -1,27 +1,26 @@ use cosmwasm_std::{ - ensure, to_json_binary, Decimal, Decimal256, DepsMut, Env, MessageInfo, Response, SubMsg, - Uint128, WasmMsg, + ensure, to_json_binary, Decimal, DepsMut, Env, MessageInfo, Response, SubMsg, Uint128, WasmMsg, }; use euclid::{ chain::{ChainUid, CrossChainUser}, error::ContractError, events::{liquidity_event, simple_event, tx_event, TxType}, - fee::MAX_FEE_BPS, + fee::{Fee, MAX_FEE_BPS}, liquidity::AddLiquidityResponse, msgs::{ virtual_balance::ExecuteTransfer, vlp::{VlpRemoveLiquidityResponse, VlpSwapResponse}, }, - pool::{Pool, PoolCreationResponse}, + pool::PoolCreationResponse, swap::NextSwapVlp, - token::{Pair, Token}, + token::{Pair, PairWithAmount, Token}, virtual_balance::BalanceKey, }; use crate::{ - query::{assert_slippage_tolerance, calculate_lp_allocation, calculate_swap}, + query::{calculate_lp_allocation_for_liquidity, calculate_swap, extract_token_amount}, reply::{NEXT_SWAP_REPLY_ID, VIRTUAL_BALANCE_TRANSFER_REPLY_ID}, - state::{self, BALANCES, CHAIN_LP_TOKENS, STATE}, + state::{self, State, BALANCES, CHAIN_LP_TOKENS, STATE}, }; /// Registers a new pool in the contract. Function called by Router Contract @@ -68,6 +67,9 @@ pub fn register_pool( let ack = PoolCreationResponse { vlp_contract: env.contract.address.to_string(), + tx_id: tx_id.clone(), + mint_lp_tokens: Uint128::zero(), + sender: sender.clone(), }; Ok(Response::new() @@ -81,6 +83,42 @@ pub fn register_pool( .set_data(to_json_binary(&ack)?)) } +pub fn register_pool_with_funds( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + sender: CrossChainUser, + pair_with_amount: PairWithAmount, + slippage_tolerance_bps: u64, + tx_id: String, +) -> Result { + let state = STATE.load(deps.storage)?; + ensure!(info.sender == state.router, ContractError::Unauthorized {}); + // Verify that chain pool does not already exist + ensure!( + !CHAIN_LP_TOKENS.has(deps.storage, sender.chain_uid.clone()), + ContractError::PoolAlreadyExists {} + ); + // Check for token id + ensure!( + state.pair.get_tupple() == pair_with_amount.get_pair()?.get_tupple(), + ContractError::AssetDoesNotExist {} + ); + // Store the pool in the map + CHAIN_LP_TOKENS.save(deps.storage, sender.chain_uid.clone(), &Uint128::zero())?; + + // Add liquidity part // + add_liquidity( + deps.branch(), + env, + info, + sender, + pair_with_amount, + slippage_tolerance_bps, + tx_id, + ) +} + /// Adds liquidity to the VLP /// /// # Arguments @@ -102,56 +140,55 @@ pub fn add_liquidity( env: Env, info: MessageInfo, sender: CrossChainUser, - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - slippage_tolerance: u64, + liquidity: PairWithAmount, + slippage_tolerance_bps: u64, tx_id: String, ) -> Result { let mut state = STATE.load(deps.storage)?; ensure!(info.sender == state.router, ContractError::Unauthorized {}); - let mut chain_lp_tokens = CHAIN_LP_TOKENS.load(deps.storage, sender.chain_uid.clone())?; + // Ensure tokens are received by VLP + for token in liquidity.get_vec_token() { + let token_reserve = BALANCES.load(deps.storage, token.token.clone())?; + + // Router mints new tokens or this vlp gets new balance from token transfer by previous, so virtual_balance = amount_in + pool_current_liquidity + let vlp_virtual_balance_balance: euclid::msgs::virtual_balance::GetBalanceResponse = + deps.querier.query_wasm_smart( + state.virtual_balance.clone(), + &euclid::msgs::virtual_balance::QueryMsg::GetBalance { + balance_key: BalanceKey { + cross_chain_user: CrossChainUser { + address: env.contract.address.to_string(), + chain_uid: ChainUid::vsl_chain_uid()?, + }, + token_id: token.token.to_string(), + }, + }, + )?; - let pair = state.pair.clone(); + ensure!( + vlp_virtual_balance_balance.amount == token_reserve.checked_add(token.amount)?, + ContractError::new("Liquidity didn't receive enough funds!") + ); + } - // Verify that ratio of assets provided is equal to the ratio of assets in the pool - let ratio = - Decimal256::checked_from_ratio(token_1_liquidity, token_2_liquidity).map_err(|err| { - ContractError::Generic { - err: err.to_string(), - } - })?; + let pair = state.pair.clone(); let mut total_reserve_1 = BALANCES.load(deps.storage, pair.token_1.clone())?; let mut total_reserve_2 = BALANCES.load(deps.storage, pair.token_2.clone())?; - // Lets get lq ratio, it will be the current ratio of token reserves or if its first time then it will be ratio of tokens provided - let lq_ratio = - Decimal256::checked_from_ratio(total_reserve_1, total_reserve_2).unwrap_or(ratio); - - // Verify slippage tolerance is between 0 and 100 - ensure!( - slippage_tolerance.le(&100), - ContractError::InvalidSlippageTolerance {} - ); - - assert_slippage_tolerance(ratio, lq_ratio, slippage_tolerance)?; + let (token_1_liquidity, token_2_liquidity) = extract_token_amount(&liquidity, &pair); - // Calculate liquidity added share for LP provider from total liquidity - let lp_allocation = calculate_lp_allocation( + let lp_allocation = calculate_lp_allocation_for_liquidity( token_1_liquidity, token_2_liquidity, total_reserve_1, total_reserve_2, state.total_lp_tokens, + Some(slippage_tolerance_bps), )?; - ensure!( - !lp_allocation.is_zero(), - ContractError::Generic { - err: "LP Allocation cannot be zero".to_string() - } - ); + let mut chain_lp_tokens = CHAIN_LP_TOKENS.load(deps.storage, sender.chain_uid.clone())?; chain_lp_tokens = chain_lp_tokens.checked_add(lp_allocation)?; CHAIN_LP_TOKENS.save(deps.storage, sender.chain_uid.clone(), &chain_lp_tokens)?; @@ -159,44 +196,42 @@ pub fn add_liquidity( // Add to total liquidity and total lp allocation total_reserve_1 = total_reserve_1.checked_add(token_1_liquidity)?; total_reserve_2 = total_reserve_2.checked_add(token_2_liquidity)?; - state.total_lp_tokens = state.total_lp_tokens.checked_add(lp_allocation)?; - STATE.save(deps.storage, &state)?; + STATE.save(deps.storage, &state)?; BALANCES.save(deps.storage, pair.token_1.clone(), &total_reserve_1)?; - BALANCES.save(deps.storage, pair.token_2.clone(), &total_reserve_2)?; - // Add current balance to SNAPSHOT MAP + let res = Response::new() + .add_event(liquidity_event( + &pair + .get_pair_with_amount(total_reserve_1, total_reserve_2)? + .get_vec_token(), + &liquidity.get_vec_token(), + &tx_id, + )) + .add_attribute("sender", sender.to_sender_string()) + .add_attribute("lp_allocation", lp_allocation) + .add_attribute("liquidity_1_added", token_1_liquidity) + .add_attribute("liquidity_2_added", token_2_liquidity); // Prepare Liquidity Response let liquidity_response = AddLiquidityResponse { mint_lp_tokens: lp_allocation, vlp_address: env.contract.address.to_string(), + tx_id: tx_id.clone(), + sender: sender.clone(), }; - // Prepare acknowledgement - let acknowledgement = to_json_binary(&liquidity_response)?; - - let pool = Pool { - pair, - reserve_1: total_reserve_1, - reserve_2: total_reserve_2, - }; - - Ok(Response::new() + let ack = to_json_binary(&liquidity_response)?; + Ok(res + .add_attribute("action", "add_liquidity") .add_event(tx_event( &tx_id, &sender.to_sender_string(), TxType::AddLiquidity, )) - .add_event(liquidity_event(&pool, &tx_id)) - .add_attribute("action", "add_liquidity") - .add_attribute("sender", sender.to_sender_string()) - .add_attribute("lp_allocation", lp_allocation) - .add_attribute("liquidity_1_added", token_1_liquidity) - .add_attribute("liquidity_2_added", token_2_liquidity) - .set_data(acknowledgement)) + .set_data(ack)) } /// Removes liquidity from the VLP @@ -246,6 +281,8 @@ pub fn remove_liquidity( // Calculate tokens_2 to send let token_2_liquidity = total_reserve_2.checked_mul_ceil(lp_share)?; + let liquidity_released = pair.get_pair_with_amount(token_1_liquidity, token_2_liquidity)?; + total_reserve_1 = total_reserve_1.checked_sub(token_1_liquidity)?; total_reserve_2 = total_reserve_2.checked_sub(token_2_liquidity)?; @@ -258,23 +295,18 @@ pub fn remove_liquidity( // Prepare Liquidity Response let liquidity_response = VlpRemoveLiquidityResponse { - token_1_liquidity_released: token_1_liquidity, - token_2_liquidity_released: token_2_liquidity, burn_lp_tokens: lp_allocation, + //TODO + preferred_denom: None, tx_id: tx_id.clone(), sender: sender.clone(), vlp_address: env.contract.address.to_string(), + liquidity_released: liquidity_released.clone(), }; // Prepare acknowledgement let acknowledgement = to_json_binary(&liquidity_response)?; - let pool = Pool { - pair: pair.clone(), - reserve_1: total_reserve_1, - reserve_2: total_reserve_2, - }; - let vlp_cross_chain_struct = CrossChainUser { address: env.contract.address.to_string(), chain_uid: ChainUid::vsl_chain_uid()?, @@ -308,7 +340,13 @@ pub fn remove_liquidity( token_2_transfer_msg, VIRTUAL_BALANCE_TRANSFER_REPLY_ID, )) - .add_event(liquidity_event(&pool, &tx_id)) + .add_event(liquidity_event( + &pair + .get_pair_with_amount(total_reserve_1, total_reserve_2)? + .get_vec_token(), + &liquidity_released.get_vec_token(), + &tx_id, + )) .add_attribute("action", "remove_liquidity") .add_attribute("sender", sender.to_sender_string()) .add_attribute("token_1_removed_liquidity", token_1_liquidity) @@ -317,6 +355,7 @@ pub fn remove_liquidity( .set_data(acknowledgement)) } +#[allow(clippy::too_many_arguments)] pub fn execute_swap( deps: DepsMut, env: Env, @@ -340,25 +379,6 @@ pub fn execute_swap( let pair = state.pair.clone(); ensure!(asset_in.exists(pair), ContractError::AssetDoesNotExist {}); - - // Get Fee from the state - let fee = state.clone().fee; - - let lp_fee = amount_in.checked_mul_floor(Decimal::bps(fee.lp_fee_bps))?; - let euclid_fee = amount_in.checked_mul_floor(Decimal::bps(fee.euclid_fee_bps))?; - - // Add the lp fee to total fees - state - .total_fees_collected - .lp_fees - .add_fee(asset_in.to_string(), lp_fee); - - // Calcuate the sum of fees - let total_fee = lp_fee.checked_add(euclid_fee)?; - - // Calculate the amount of asset to be swapped - let swap_amount = amount_in.checked_sub(total_fee)?; - let asset_out = state.pair.get_other_token(asset_in.clone()); let mut token_in_reserve = BALANCES.load(deps.storage, asset_in.clone())?; @@ -384,6 +404,24 @@ pub fn execute_swap( ContractError::new("Swap didn't receive any funds!") ); + // Get Fee from the state + let fee = state.clone().fee; + + let lp_fee = amount_in.checked_mul_floor(Decimal::bps(fee.lp_fee_bps))?; + let euclid_fee = amount_in.checked_mul_floor(Decimal::bps(fee.euclid_fee_bps))?; + + // Add the lp fee to total fees + state + .total_fees_collected + .lp_fees + .add_fee(asset_in.to_string(), lp_fee); + + // Calcuate the sum of fees + let total_fee = lp_fee.checked_add(euclid_fee)?; + + // Calculate the amount of asset to be swapped + let swap_amount = amount_in.checked_sub(total_fee)?; + let receive_amount = calculate_swap(swap_amount, token_in_reserve, token_out_reserve)?; // Verify that the receive amount is greater than 0 to be eligible for any swap @@ -403,20 +441,6 @@ pub fn execute_swap( BALANCES.save(deps.storage, asset_in.clone(), &token_in_reserve)?; BALANCES.save(deps.storage, asset_out.clone(), &token_out_reserve)?; - let pool = Pool { - pair: state.pair.clone(), - reserve_1: if state.pair.token_1 == asset_in { - token_in_reserve - } else { - token_out_reserve - }, - reserve_2: if state.pair.token_1 == asset_out { - token_out_reserve - } else { - token_in_reserve - }, - }; - // Finalize ack response to swap pool let swap_response = VlpSwapResponse { sender: sender.clone(), @@ -571,7 +595,17 @@ pub fn execute_swap( Ok(response .add_event(tx_event(&tx_id, &sender.to_sender_string(), TxType::Swap)) - .add_event(liquidity_event(&pool, &tx_id)) + .add_event(liquidity_event( + &[ + asset_in.with_amount(token_in_reserve), + asset_out.with_amount(token_out_reserve), + ], + &[ + asset_in.with_amount(swap_amount.checked_add(lp_fee)?), + asset_out.with_amount(receive_amount), + ], + &tx_id, + )) .add_attribute("action", "swap") .add_attribute("amount_in", amount_in) .add_attribute("asset_in", asset_in.to_string()) @@ -611,3 +645,56 @@ pub fn update_fee( .add_event(simple_event()) .add_attribute("action", "update_fee")) } + +pub fn update_state( + deps: DepsMut, + info: MessageInfo, + router: Option, + virtual_balance: Option, + fee: Option, + last_updated: Option, + admin: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + ensure!(info.sender == state.admin, ContractError::Unauthorized {}); + // Verify that the router is a valid address + let verified_router = if let Some(router) = router { + deps.api.addr_validate(&router)?; + router + } else { + state.router + }; + + // Verify that the virtual balance is a valid address + let verified_virtual_balance = if let Some(virtual_balance) = virtual_balance { + deps.api.addr_validate(&virtual_balance)?; + virtual_balance + } else { + state.virtual_balance + }; + + // Verify that the admin is a valid address + let verified_admin = if let Some(admin) = admin { + deps.api.addr_validate(&admin)?; + admin + } else { + state.admin + }; + + let new_state = State { + pair: state.pair, + router: verified_router, + virtual_balance: verified_virtual_balance, + fee: fee.unwrap_or(state.fee), + total_fees_collected: state.total_fees_collected, + last_updated: last_updated.unwrap_or(state.last_updated), + total_lp_tokens: state.total_lp_tokens, + admin: verified_admin, + }; + + STATE.save(deps.storage, &new_state)?; + + Ok(Response::new() + .add_event(simple_event()) + .add_attribute("action", "update_state")) +} diff --git a/contracts/hub/vlp/src/interface.rs b/contracts/hub/vlp/src/interface.rs new file mode 100644 index 00000000..e21ac175 --- /dev/null +++ b/contracts/hub/vlp/src/interface.rs @@ -0,0 +1,17 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::vlp::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "vlp_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)] +pub struct VlpContract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for VlpContract { + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(crate::contract::reply), + ) + } +} diff --git a/contracts/hub/vlp/src/lib.rs b/contracts/hub/vlp/src/lib.rs index 780c61ff..7de48d71 100644 --- a/contracts/hub/vlp/src/lib.rs +++ b/contracts/hub/vlp/src/lib.rs @@ -8,3 +8,8 @@ pub mod state; pub mod mock; #[cfg(test)] mod tests; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::VlpContract; diff --git a/contracts/hub/vlp/src/mock.rs b/contracts/hub/vlp/src/mock.rs index 9c036524..6d7a4143 100644 --- a/contracts/hub/vlp/src/mock.rs +++ b/contracts/hub/vlp/src/mock.rs @@ -14,6 +14,7 @@ impl MockVlp { pub fn addr(&self) -> &Addr { &self.0 } + #[allow(clippy::too_many_arguments)] pub fn instantiate( app: &mut MockApp, code_id: u64, diff --git a/contracts/hub/vlp/src/query.rs b/contracts/hub/vlp/src/query.rs index 6fedeec2..fb979c50 100644 --- a/contracts/hub/vlp/src/query.rs +++ b/contracts/hub/vlp/src/query.rs @@ -3,9 +3,10 @@ use cosmwasm_std::{ }; use euclid::chain::ChainUid; use euclid::error::ContractError; +use euclid::fee::BPS_100_PERCENT; use euclid::pool::MINIMUM_LIQUIDITY; use euclid::swap::NextSwapVlp; -use euclid::token::Token; +use euclid::token::{Pair, PairWithAmount, Token}; use euclid::msgs::vlp::{ AllPoolsResponse, FeeResponse, GetLiquidityResponse, GetStateResponse, GetSwapResponse, @@ -218,14 +219,97 @@ pub fn calculate_lp_allocation( pub fn assert_slippage_tolerance( ratio: Decimal256, pool_ratio: Decimal256, - slippage_tolerance: u64, + slippage_tolerance_bps: u64, ) -> Result { - let slippage = pool_ratio.abs_diff(ratio); - let slippage_tolerance = - Decimal256::from_ratio(Uint128::from(slippage_tolerance), Uint128::from(100u128)); + let slippage = ratio.abs_diff(pool_ratio).checked_div(pool_ratio)?; + + let slippage_tolerance = Decimal256::bps(slippage_tolerance_bps); ensure!( slippage.le(&slippage_tolerance), - ContractError::LiquiditySlippageExceeded {} + ContractError::LiquiditySlippageExceeded { + expected: slippage, + received: slippage_tolerance, + } ); Ok(true) } + +/// Calculates the LP allocation for provided liquidity amounts +/// +/// # Arguments +/// +/// * `liquidity` - The pair of tokens with amounts being provided as liquidity +/// * `pair` - The token pair configuration for the pool +/// * `total_reserve_1` - Current total reserve of token 1 +/// * `total_reserve_2` - Current total reserve of token 2 +/// * `total_lp_tokens` - Total LP tokens currently in circulation +/// * `slippage_tolerance_bps` - Slippage tolerance in basis points (optional) +/// +/// # Returns +/// +/// Returns the calculated LP allocation amount +pub fn calculate_lp_allocation_for_liquidity( + token_1_liquidity: Uint128, + token_2_liquidity: Uint128, + total_reserve_1: Uint128, + total_reserve_2: Uint128, + total_lp_tokens: Uint128, + slippage_tolerance_bps: Option, +) -> Result { + // Verify that ratio of assets provided is equal to the ratio of assets in the pool + let ratio = + Decimal256::checked_from_ratio(token_1_liquidity, token_2_liquidity).map_err(|err| { + ContractError::Generic { + err: err.to_string(), + } + })?; + + // Get liquidity ratio (current ratio of token reserves or provided ratio if first time) + let lq_ratio = + Decimal256::checked_from_ratio(total_reserve_1, total_reserve_2).unwrap_or(ratio); + + // Check slippage if tolerance is provided + if let Some(slippage_tolerance_bps) = slippage_tolerance_bps { + ensure!( + slippage_tolerance_bps.le(&BPS_100_PERCENT), + ContractError::InvalidSlippageTolerance {} + ); + assert_slippage_tolerance(ratio, lq_ratio, slippage_tolerance_bps)?; + } + + // Calculate liquidity added share for LP provider from total liquidity + let lp_allocation = calculate_lp_allocation( + token_1_liquidity, + token_2_liquidity, + total_reserve_1, + total_reserve_2, + total_lp_tokens, + )?; + + ensure!( + !lp_allocation.is_zero(), + ContractError::Generic { + err: "LP Allocation cannot be zero".to_string() + } + ); + + Ok(lp_allocation) +} + +/// Extracts the token amount for a given token from a pair with amounts +/// by matching it against a reference token +pub fn extract_token_amount(liquidity: &PairWithAmount, pair: &Pair) -> (Uint128, Uint128) { + let token_1_liquidity = if liquidity.token_1.token == pair.token_1 { + liquidity.token_1.amount + } else { + liquidity.token_2.amount + }; + + let token_2_liquidity = if liquidity.token_2.token == pair.token_2 { + liquidity.token_2.amount + } else { + liquidity.token_1.amount + }; + + (token_1_liquidity, token_2_liquidity) +} diff --git a/contracts/hub/vlp/src/tests.rs b/contracts/hub/vlp/src/tests.rs index d545e12e..19fc8e17 100644 --- a/contracts/hub/vlp/src/tests.rs +++ b/contracts/hub/vlp/src/tests.rs @@ -1,6 +1,8 @@ +#[allow(clippy::module_inception)] #[cfg(test)] mod tests { use crate::contract::{execute, instantiate}; + use crate::query::calculate_lp_allocation_for_liquidity; use crate::state::{State, BALANCES, CHAIN_LP_TOKENS, STATE}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, DepsMut, Response, Uint128}; @@ -180,4 +182,55 @@ mod tests { ContractError::new("Euclid Fee cannot exceed maximum limit") ); } + + #[test] + fn test_calculate_lp_allocation_for_liquidity() { + let token_1_liquidity = Uint128::new(980); + let token_2_liquidity = Uint128::new(1000); + let total_reserve_1 = Uint128::new(10000); + let total_reserve_2 = Uint128::new(10000); + let total_lp_tokens = Uint128::new(10000); // 1:1 ratio for lp tokens to tokens present + let slippage_tolerance_bps = Some(200); // 2% slippage tolerance + + // Call the function to test + let lp_allocation = calculate_lp_allocation_for_liquidity( + token_1_liquidity, + token_2_liquidity, + total_reserve_1, + total_reserve_2, + total_lp_tokens, + slippage_tolerance_bps, + ) + .unwrap(); + + // Assert the expected LP allocation + let expected_allocation = Uint128::new(980); // This value should be calculated based on the logic + assert_eq!(lp_allocation, expected_allocation); + } + + #[test] + fn test_calculate_lp_allocation_for_liquidity_exceeded_slippage() { + let token_1_liquidity = Uint128::new(100); + let token_2_liquidity = Uint128::new(99); + let total_reserve_1 = Uint128::new(100); + let total_reserve_2 = Uint128::new(100); + let total_lp_tokens = Uint128::new(100); + let slippage_tolerance_bps = Some(0); // 0% slippage tolerance to force failure + + // Call the function to test and expect an error + let err = calculate_lp_allocation_for_liquidity( + token_1_liquidity, + token_2_liquidity, + total_reserve_1, + total_reserve_2, + total_lp_tokens, + slippage_tolerance_bps, + ) + .unwrap_err(); + + match err { + ContractError::LiquiditySlippageExceeded { .. } => (), + _ => panic!("Expected slippage exceeded error, got {:?}", err), + } + } } diff --git a/contracts/liquidity/cw20/Cargo.toml b/contracts/liquidity/cw20/Cargo.toml index ad188190..e1dbd383 100644 --- a/contracts/liquidity/cw20/Cargo.toml +++ b/contracts/liquidity/cw20/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "cw20" -version = "0.1.0" -authors = ["Anshudhar Kumar Singh "] +version = "0.2.1" +authors = [ + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -49,6 +52,7 @@ schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } +cw-orch = "=0.24.1" [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/liquidity/cw20/src/contract.rs b/contracts/liquidity/cw20/src/contract.rs index 6ed2f9ef..29d6ec54 100644 --- a/contracts/liquidity/cw20/src/contract.rs +++ b/contracts/liquidity/cw20/src/contract.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Resp use cw2::set_contract_version; use euclid::msgs::escrow::Cw20InstantiateResponse; +use crate::execute::execute_update_state; use crate::state::{State, STATE}; use euclid::error::ContractError; use euclid::msgs::cw20::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -49,63 +50,17 @@ pub fn execute( info: MessageInfo, msg: ExecuteMsg, ) -> Result { - Ok(execute_cw20(deps, env, info, msg.into())?) - // match msg { - // ExecuteMsg::Transfer { recipient, amount } => Ok(execute_cw20( - // deps, - // env, - // info, - // Cw20ExecuteMsg::Transfer { recipient, amount }, - // )?), - // ExecuteMsg::Burn { amount } => todo!(), - // ExecuteMsg::Send { - // contract, - // amount, - // msg, - // } => todo!(), - // ExecuteMsg::IncreaseAllowance { - // spender, - // amount, - // expires, - // } => todo!(), - // ExecuteMsg::DecreaseAllowance { - // spender, - // amount, - // expires, - // } => todo!(), - // ExecuteMsg::TransferFrom { - // owner, - // recipient, - // amount, - // } => todo!(), - // ExecuteMsg::SendFrom { - // owner, - // contract, - // amount, - // msg, - // } => todo!(), - // ExecuteMsg::BurnFrom { owner, amount } => todo!(), - // ExecuteMsg::Mint { recipient, amount } => todo!(), - // ExecuteMsg::UpdateMarketing { - // project, - // description, - // marketing, - // } => todo!(), - // ExecuteMsg::UploadLogo(_) => todo!(), - // } + match msg { + ExecuteMsg::UpdateState { + token_pair, + factory_address, + vlp, + } => execute_update_state(deps, env, info, token_pair, factory_address, vlp), + _ => Ok(execute_cw20(deps, env, info, msg.into())?), + } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { Ok(cw20_query(deps, env, msg.into())?) - // match msg { - // QueryMsg::TokenInfo { } => todo!(), - // QueryMsg::Minter { } => todo!(), - // QueryMsg::Allowance { owner, spender } => todo!(), - // QueryMsg::AllAllowances { owner, start_after, limit } => todo!(), - // QueryMsg::AllAccounts { start_after, limit } => todo!(), - // QueryMsg::MarketingInfo { } => todo!(), - // QueryMsg::DownloadLogo { } => todo!(), - // QueryMsg::Balance { address } => todo!(), - // } } diff --git a/contracts/liquidity/cw20/src/execute.rs b/contracts/liquidity/cw20/src/execute.rs index 8b137891..2c8c3c22 100644 --- a/contracts/liquidity/cw20/src/execute.rs +++ b/contracts/liquidity/cw20/src/execute.rs @@ -1 +1,39 @@ +use cosmwasm_std::{ensure, Addr, DepsMut, Env, MessageInfo, Response}; +use euclid::{error::ContractError, token::Pair}; +use crate::state::{State, STATE}; + +pub fn execute_update_state( + deps: DepsMut, + _env: Env, + info: MessageInfo, + token_pair: Option, + factory_address: Option, + vlp: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + ensure!( + state.factory_address == info.sender.into_string(), + ContractError::Unauthorized {} + ); + + STATE.save( + deps.storage, + &State { + token_pair: token_pair.unwrap_or(state.token_pair), + factory_address: factory_address.clone().unwrap_or(state.factory_address), + vlp: vlp.clone().unwrap_or(state.vlp), + }, + )?; + + Ok(Response::new() + .add_attribute("method", "update_state") + .add_attribute( + "factory_address", + factory_address.map_or("unchanged".to_string(), |x| x.to_string()), + ) + .add_attribute( + "vlp", + vlp.map_or("unchanged".to_string(), |x| x.to_string()), + )) +} diff --git a/contracts/liquidity/cw20/src/interface.rs b/contracts/liquidity/cw20/src/interface.rs new file mode 100644 index 00000000..a9a994e7 --- /dev/null +++ b/contracts/liquidity/cw20/src/interface.rs @@ -0,0 +1,14 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::cw20::{ExecuteMsg, InstantiateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "cw20_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, Empty, id = CONTRACT_ID)] +pub struct Cw20Contract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for Cw20Contract { + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/contracts/liquidity/cw20/src/lib.rs b/contracts/liquidity/cw20/src/lib.rs index 751fa75b..3111d458 100644 --- a/contracts/liquidity/cw20/src/lib.rs +++ b/contracts/liquidity/cw20/src/lib.rs @@ -6,3 +6,8 @@ pub mod query; pub mod state; mod test; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::Cw20Contract; diff --git a/contracts/liquidity/cw20/src/migrate.rs b/contracts/liquidity/cw20/src/migrate.rs index 6dea066b..810bc775 100644 --- a/contracts/liquidity/cw20/src/migrate.rs +++ b/contracts/liquidity/cw20/src/migrate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{entry_point, DepsMut, Env, Response}; -use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; +use euclid::{error::ContractError, msgs::cw20::MigrateMsg}; /// This is the migrate entry point for the contract. /// Currently, it does not perform any migration logic and simply returns an empty response. diff --git a/contracts/liquidity/escrow/Cargo.toml b/contracts/liquidity/escrow/Cargo.toml index 6ffe6edf..6295ceea 100644 --- a/contracts/liquidity/escrow/Cargo.toml +++ b/contracts/liquidity/escrow/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "escrow" -version = "0.1.0" -authors = ["gachouchani1999 "] +version = "0.2.2" +authors = [ + "gachouchani1999 ", + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -36,10 +40,10 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true, features = [ - "cosmwasm_1_2", - # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher - # "cosmwasm_1_4", - "ibc3", + "cosmwasm_1_2", + # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher + # "cosmwasm_1_4", + "ibc3", ] } cw-storage-plus = { workspace = true } cw2 = { workspace = true } @@ -48,7 +52,9 @@ schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } +cw-orch = "=0.24.1" + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true } -mock = { workspace = true } \ No newline at end of file +mock = { workspace = true } diff --git a/contracts/liquidity/escrow/src/contract.rs b/contracts/liquidity/escrow/src/contract.rs index 1d527ba2..694a5d98 100644 --- a/contracts/liquidity/escrow/src/contract.rs +++ b/contracts/liquidity/escrow/src/contract.rs @@ -14,6 +14,7 @@ use crate::execute::{ execute_withdraw, receive_cw20, }; use crate::query::{self, query_token_id}; +use crate::reply::{handle_refund, FORWARDING_MESSAGE_REPLY_ID}; use crate::state::{State, STATE}; use euclid::msgs::escrow::{EscrowInstantiateResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -67,29 +68,42 @@ pub fn execute( ExecuteMsg::AddAllowedDenom { denom } => execute_add_allowed_denom(deps, env, info, denom), ExecuteMsg::DisallowDenom { denom } => execute_disallow_denom(deps, env, info, denom), ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::Withdraw { recipient, amount } => { - execute_withdraw(deps, env, info, recipient, amount) - } + ExecuteMsg::Withdraw { + recipient, + amount, + preferred_denom, + forwarding_message, + refund_address, + } => execute_withdraw( + deps, + env, + info, + recipient, + amount, + preferred_denom, + forwarding_message, + refund_address, + ), } } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { - // New escrow queries + QueryMsg::State {} => query::query_state(deps), QueryMsg::TokenId {} => query_token_id(deps), QueryMsg::TokenAllowed { denom } => query::query_token_allowed(deps, denom), QueryMsg::AllowedDenoms {} => query::query_allowed_denoms(deps), } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result { +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { let id = msg.id; - Err(ContractError::Std(StdError::generic_err(format!( - "Unknown reply id: {}", - id - )))) + match id { + FORWARDING_MESSAGE_REPLY_ID => handle_refund(deps, msg), + _ => Err(ContractError::Std(StdError::generic_err(format!( + "Unknown reply id: {}", + id + )))), + } } - -#[cfg(test)] -mod tests {} diff --git a/contracts/liquidity/escrow/src/execute.rs b/contracts/liquidity/escrow/src/execute.rs index f5938467..59bdb705 100644 --- a/contracts/liquidity/escrow/src/execute.rs +++ b/contracts/liquidity/escrow/src/execute.rs @@ -1,11 +1,18 @@ use cosmwasm_std::{ - ensure, from_json, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, Uint128, + ensure, from_json, Addr, CosmosMsg, DepsMut, Env, MessageInfo, Response, SubMsg, Uint128, }; use cw20::Cw20ReceiveMsg; -use euclid::{cw20::Cw20HookMsg, error::ContractError, token::TokenType}; +use euclid::{ + error::ContractError, + msgs::{escrow::cw20::EscrowCw20HookMsg, hook::EuclidReceive}, + token::TokenType, +}; -use crate::state::{ALLOWED_DENOMS, DENOM_TO_AMOUNT, STATE}; +use crate::{ + reply::FORWARDING_MESSAGE_REPLY_ID, + state::{ALLOWED_DENOMS, DENOM_TO_AMOUNT, REFUND_ADDRESS, REFUND_ASSETS, STATE}, +}; pub fn execute_add_allowed_denom( deps: DepsMut, @@ -13,6 +20,9 @@ pub fn execute_add_allowed_denom( info: MessageInfo, denom: TokenType, ) -> Result { + // Vouchers are not escrowed + ensure!(!denom.is_voucher(), ContractError::CannotEscrowVoucher {}); + // TODO nonpayable to this function? would be better to limit depositing funds through the deposit functions // Only the factory can call this function let factory_address = STATE.load(deps.storage)?.factory_address; @@ -138,7 +148,7 @@ pub fn receive_cw20( cw20_msg: Cw20ReceiveMsg, ) -> Result { match from_json(&cw20_msg.msg)? { - Cw20HookMsg::Deposit {} => { + EscrowCw20HookMsg::Deposit {} => { let factory_address = STATE.load(deps.storage)?.factory_address; // Only the factory can call this function let sender = cw20_msg.sender; @@ -157,7 +167,6 @@ pub fn receive_cw20( execute_deposit_cw20(deps, env, info, amount_sent, asset_sent) } - _ => Err(ContractError::UnsupportedMessage {}), } } @@ -168,6 +177,8 @@ pub fn execute_deposit_cw20( amount: Uint128, denom: TokenType, ) -> Result { + ensure!(denom.is_smart(), ContractError::UnsupportedDenomination {}); + // Non-zero and unauthorized checks were made in receive_cw20 let allowed_denoms = ALLOWED_DENOMS.load(deps.storage)?; @@ -206,9 +217,25 @@ pub fn execute_withdraw( info: MessageInfo, recipient: Addr, amount: Uint128, + preferred_denom: Option, + forwarding_message: Option, + refund_address: Option, ) -> Result { + // Clean any old refund address + REFUND_ADDRESS.remove(deps.storage); + REFUND_ASSETS.remove(deps.storage); + // Only the factory can call this function let mut state = STATE.load(deps.storage)?; + if let Some(ref refund_address) = refund_address { + deps.api + .addr_validate(refund_address) + .map_err(|_| ContractError::InvalidAddress { + address: refund_address.to_string(), + msg: "Invalid refund address".to_string(), + })?; + REFUND_ADDRESS.save(deps.storage, refund_address)?; + } ensure!( info.sender == state.factory_address, ContractError::Unauthorized {} @@ -217,9 +244,19 @@ pub fn execute_withdraw( ensure!(!amount.is_zero(), ContractError::ZeroWithdrawalAmount {}); let mut messages: Vec = Vec::new(); + let mut forwarding_messages: Vec = Vec::new(); + let mut remaining_withdraw_amount = amount; let mut allowed_denoms = ALLOWED_DENOMS.load(deps.storage)?.into_iter().peekable(); + if let Some(preferred_denom) = preferred_denom { + ensure!( + allowed_denoms.any(|denom| denom.get_key() == preferred_denom.get_key()), + ContractError::UnsupportedDenomination {} + ); + + // Only allow the preferred denom, remove all other denoms + allowed_denoms = vec![preferred_denom].into_iter().peekable(); + } - let mut remaining_withdraw_amount = amount; // Ensure that the amount desired doesn't exceed the current balance while !remaining_withdraw_amount.is_zero() && allowed_denoms.peek().is_some() { let denom = allowed_denoms @@ -234,8 +271,6 @@ pub fn execute_withdraw( remaining_withdraw_amount }; - let send_msg = denom.create_transfer_msg(transfer_amount, recipient.to_string(), None)?; - messages.push(send_msg); remaining_withdraw_amount = remaining_withdraw_amount.checked_sub(transfer_amount)?; DENOM_TO_AMOUNT.save( @@ -243,6 +278,26 @@ pub fn execute_withdraw( denom.get_key(), &denom_balance.checked_sub(transfer_amount)?, )?; + + // Wrap the forwading message into EuclidReceive Cosmos Msg + let forwarding_message = match &forwarding_message { + Some(forwarding_msg) => Some(forwarding_msg.to_receiver_msg()?), + None => None, + }; + let send_msg = denom.create_transfer_msg( + transfer_amount, + recipient.to_string(), + None, + forwarding_message.clone(), + )?; + if forwarding_message.is_some() { + forwarding_messages.push(SubMsg::reply_always(send_msg, FORWARDING_MESSAGE_REPLY_ID)); + let mut refund_assets = REFUND_ASSETS.load(deps.storage).unwrap_or_default(); + refund_assets.push((denom, transfer_amount)); + REFUND_ASSETS.save(deps.storage, &refund_assets)?; + } else { + messages.push(send_msg); + } } // After all the transfer messages, ensure that total amount that needs to be sent is zero @@ -253,10 +308,13 @@ pub fn execute_withdraw( state.total_amount = state.total_amount.checked_sub(amount)?; STATE.save(deps.storage, &state)?; - - Ok(Response::new() + let response = Response::new() .add_messages(messages) - .add_attribute("method", "withdraw") + .add_submessages(forwarding_messages) + .add_attribute("method", "escrow_withdraw") .add_attribute("amount", amount) - .add_attribute("recipient", recipient)) + .add_attribute("token", state.token_id.to_string()) + .add_attribute("recipient", recipient); + + Ok(response) } diff --git a/contracts/liquidity/escrow/src/interface.rs b/contracts/liquidity/escrow/src/interface.rs new file mode 100644 index 00000000..a7c90a33 --- /dev/null +++ b/contracts/liquidity/escrow/src/interface.rs @@ -0,0 +1,14 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::escrow::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "escrow_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)] +pub struct EscrowContract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for EscrowContract { + fn wrapper() -> Box> { + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) + } +} diff --git a/contracts/liquidity/escrow/src/lib.rs b/contracts/liquidity/escrow/src/lib.rs index ffabe242..e6d28b65 100644 --- a/contracts/liquidity/escrow/src/lib.rs +++ b/contracts/liquidity/escrow/src/lib.rs @@ -5,6 +5,7 @@ pub mod execute; pub mod helpers; pub mod migrate; pub mod query; +pub mod reply; pub mod state; #[cfg(test)] @@ -12,3 +13,8 @@ mod tests; #[cfg(not(target_arch = "wasm32"))] pub mod mock; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::EscrowContract; diff --git a/contracts/liquidity/escrow/src/migrate.rs b/contracts/liquidity/escrow/src/migrate.rs index 6dea066b..02c58d1a 100644 --- a/contracts/liquidity/escrow/src/migrate.rs +++ b/contracts/liquidity/escrow/src/migrate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{entry_point, DepsMut, Env, Response}; -use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; +use euclid::{error::ContractError, msgs::escrow::MigrateMsg}; /// This is the migrate entry point for the contract. /// Currently, it does not perform any migration logic and simply returns an empty response. diff --git a/contracts/liquidity/escrow/src/mock.rs b/contracts/liquidity/escrow/src/mock.rs index ab51a872..52dfe7a3 100644 --- a/contracts/liquidity/escrow/src/mock.rs +++ b/contracts/liquidity/escrow/src/mock.rs @@ -3,7 +3,6 @@ use crate::contract::{execute, instantiate, query, reply}; use cosmwasm_std::{to_json_binary, Addr, Coin, CosmosMsg, Empty, WasmMsg}; use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; - use euclid::{ msgs::escrow::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenIdResponse}, token::{Token, TokenType}, diff --git a/contracts/liquidity/escrow/src/query.rs b/contracts/liquidity/escrow/src/query.rs index dd76f6ed..8bd2e050 100644 --- a/contracts/liquidity/escrow/src/query.rs +++ b/contracts/liquidity/escrow/src/query.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; use euclid::{ error::ContractError, - msgs::escrow::{AllowedDenomsResponse, AllowedTokenResponse, TokenIdResponse}, + msgs::escrow::{AllowedDenomsResponse, AllowedTokenResponse, StateResponse, TokenIdResponse}, token::TokenType, }; @@ -34,3 +34,15 @@ pub fn query_allowed_denoms(deps: Deps) -> Result { Ok(to_json_binary(&response)?) } + +// Returns the allowed denoms +pub fn query_state(deps: Deps) -> Result { + let state = STATE.load(deps.storage)?; + let response = StateResponse { + token: state.token_id, + factory_address: state.factory_address, + total_amount: state.total_amount, + }; + + Ok(to_json_binary(&response)?) +} diff --git a/contracts/liquidity/escrow/src/reply.rs b/contracts/liquidity/escrow/src/reply.rs new file mode 100644 index 00000000..9999e889 --- /dev/null +++ b/contracts/liquidity/escrow/src/reply.rs @@ -0,0 +1,41 @@ +use crate::state::{REFUND_ADDRESS, REFUND_ASSETS}; +use cosmwasm_std::{DepsMut, Reply, Response, SubMsgResult}; +use euclid::error::ContractError; + +pub const FORWARDING_MESSAGE_REPLY_ID: u64 = 1; + +pub fn handle_refund(deps: DepsMut, msg: Reply) -> Result { + // Get the first refund asset and pop it from the list + let refund_assets = REFUND_ASSETS.load(deps.storage).unwrap_or_default(); + + // Remove the first refund asset from the list + let (current_refund, remaining_refund_assets) = refund_assets + .split_first() + .ok_or(ContractError::new("Didn't find any refund assets"))?; + REFUND_ASSETS.save(deps.storage, &remaining_refund_assets.to_vec())?; + + let refund_address = REFUND_ADDRESS.may_load(deps.storage)?; + + match msg.result.clone() { + SubMsgResult::Err(err) => { + if let Some(refund_address) = refund_address { + let refund_msg = current_refund.0.create_transfer_msg( + current_refund.1, + refund_address, + None, + None, + )?; + Ok(Response::new() + .add_message(refund_msg) + .add_attribute("action", "forwarding_message") + .add_attribute("error", err)) + } else { + Err(ContractError::new(&format!( + "No refund address found: Forward message failed with error: {}", + err + ))) + } + } + SubMsgResult::Ok(_res) => Ok(Response::new().add_attribute("action", "forwarding_message")), + } +} diff --git a/contracts/liquidity/escrow/src/state.rs b/contracts/liquidity/escrow/src/state.rs index 42b94618..c82c2410 100644 --- a/contracts/liquidity/escrow/src/state.rs +++ b/contracts/liquidity/escrow/src/state.rs @@ -11,5 +11,6 @@ pub struct State { pub const STATE: Item = Item::new("state"); pub const ALLOWED_DENOMS: Item> = Item::new("allowed_denoms"); - +pub const REFUND_ADDRESS: Item = Item::new("refund_address"); +pub const REFUND_ASSETS: Item> = Item::new("refund_assets"); pub const DENOM_TO_AMOUNT: Map = Map::new("denom_to_amount"); diff --git a/contracts/liquidity/escrow/src/tests.rs b/contracts/liquidity/escrow/src/tests.rs index 7dad4648..4ea9d538 100644 --- a/contracts/liquidity/escrow/src/tests.rs +++ b/contracts/liquidity/escrow/src/tests.rs @@ -313,6 +313,9 @@ fn test_execute_withdraw() { msg: ExecuteMsg::Withdraw { recipient: Addr::unchecked("recipient1".to_string()), amount: Uint128::new(50), + preferred_denom: None, + forwarding_message: None, + refund_address: None, }, expected_error: None, }, @@ -321,6 +324,9 @@ fn test_execute_withdraw() { msg: ExecuteMsg::Withdraw { recipient: Addr::unchecked("recipient1".to_string()), amount: Uint128::new(2000), + preferred_denom: None, + forwarding_message: None, + refund_address: None, }, // Use 2000 which exceeds the balance expected_error: Some(ContractError::InsufficientDeposit {}), }, @@ -329,6 +335,9 @@ fn test_execute_withdraw() { msg: ExecuteMsg::Withdraw { recipient: Addr::unchecked("recipient1".to_string()), amount: Uint128::new(50), + preferred_denom: None, + forwarding_message: None, + refund_address: None, }, expected_error: Some(ContractError::Unauthorized {}), }, diff --git a/contracts/liquidity/factory/Cargo.toml b/contracts/liquidity/factory/Cargo.toml index 09f3aaf6..3e629e81 100644 --- a/contracts/liquidity/factory/Cargo.toml +++ b/contracts/liquidity/factory/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "factory" -version = "0.1.0" -authors = ["gachouchani1999 "] +version = "0.2.3" +authors = [ + "gachouchani1999 ", + "Anshudhar Kumar Singh ", + "Joe Monem ", +] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -51,7 +55,8 @@ serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } euclid = { workspace = true } euclid-ibc = { workspace = true } +cw-orch = "=0.24.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cw-multi-test = { workspace = true } -mock = { workspace = true } \ No newline at end of file +mock = { workspace = true } diff --git a/contracts/liquidity/factory/src/bin/schema.rs b/contracts/liquidity/factory/src/bin/schema.rs index 1116f76c..4efa1813 100644 --- a/contracts/liquidity/factory/src/bin/schema.rs +++ b/contracts/liquidity/factory/src/bin/schema.rs @@ -2,8 +2,10 @@ use std::env::current_dir; use cosmwasm_schema::{export_schema_with_title, schema_for, write_api}; -use euclid::cw20::Cw20HookMsg; -use euclid::msgs::factory::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use euclid::msgs::factory::{ + cw20::FactoryCw20HookMsg, euclid_receive::FactoryEuclidReceiveHook, ExecuteMsg, InstantiateMsg, + QueryMsg, +}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -15,5 +17,10 @@ fn main() { query: QueryMsg, } - export_schema_with_title(&schema_for!(Cw20HookMsg), &out_dir, "cw20receive"); + export_schema_with_title(&schema_for!(FactoryCw20HookMsg), &out_dir, "cw20receive"); + export_schema_with_title( + &schema_for!(FactoryEuclidReceiveHook), + &out_dir, + "euclid-receive", + ); } diff --git a/contracts/liquidity/factory/src/contract.rs b/contracts/liquidity/factory/src/contract.rs index be6e4e88..8ab3dc6f 100644 --- a/contracts/liquidity/factory/src/contract.rs +++ b/contracts/liquidity/factory/src/contract.rs @@ -7,13 +7,15 @@ use cw2::set_contract_version; use euclid::chain::CrossChainUser; use euclid::error::ContractError; use euclid::fee::DenomFees; +use euclid::token::TokenType; use euclid_ibc::msg::CHAIN_IBC_EXECUTE_MSG_QUEUE_RANGE; use crate::execute::{ - add_liquidity_request, execute_native_receive_callback, execute_request_deregister_denom, - execute_request_pool_creation, execute_request_register_denom, execute_request_register_escrow, - execute_swap_request, execute_update_hub_channel, execute_withdraw_virtual_balance, - receive_cw20, + add_liquidity_request, execute_deposit_token, execute_native_receive_callback, + execute_request_deregister_denom, execute_request_pool_creation, + execute_request_register_denom, execute_swap_request, execute_transfer_virtual_balance, + execute_update_hub_channel, execute_update_state, execute_withdraw_virtual_balance, + receive_cw20, receive_euclid_native, }; use crate::query::{ get_escrow, get_lp_token_address, get_partner_fees_collected, get_vlp, pending_liquidity, @@ -73,61 +75,81 @@ pub fn execute( match msg { ExecuteMsg::AddLiquidityRequest { pair_info, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, + slippage_tolerance_bps, timeout, } => add_liquidity_request( &mut deps, info, env, pair_info, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, + slippage_tolerance_bps, timeout, ), - ExecuteMsg::ExecuteSwapRequest { - asset_in, - asset_out, - amount_in, - min_amount_out, - timeout, - swaps, - cross_chain_addresses, - partner_fee, - } => { + ExecuteMsg::ExecuteSwapRequest(msg) => { let state = STATE.load(deps.storage)?; - let sender = CrossChainUser { + let mut verified_sender = CrossChainUser { address: info.sender.to_string(), chain_uid: state.chain_uid, }; + + // If token is not a voucher, verify custom sender and use it. Using custom sender is security issue if voucher is used + if !msg.asset_in.token_type.is_voucher() { + verified_sender = msg.sender.unwrap_or(verified_sender); + } + let mut amount_in = msg.amount_in; + // If this asset is native, lets get the actual amount of funds sent because these amount can vary depending on forwarding contract swaps + if let TokenType::Native { denom } = &msg.asset_in.token_type { + amount_in = info + .funds + .iter() + .find(|fund| fund.denom == *denom) + .ok_or(ContractError::InsufficientFunds {})? + .amount; + } + execute_swap_request( &mut deps, - info, env, - sender, - asset_in, - asset_out, + info, + verified_sender, + msg.asset_in, amount_in, - min_amount_out, - swaps, - timeout, - cross_chain_addresses, - partner_fee, + msg.asset_out, + msg.min_amount_out, + msg.swaps, + msg.timeout, + msg.cross_chain_addresses, + msg.partner_fee, + ) + } + ExecuteMsg::DepositToken { + amount_in, + asset_in, + recipient, + timeout, + } => { + let state = STATE.load(deps.storage)?; + let sender = CrossChainUser { + address: info.sender.to_string(), + chain_uid: state.chain_uid, + }; + + execute_deposit_token( + &mut deps, env, info, sender, asset_in, amount_in, timeout, recipient, ) } ExecuteMsg::UpdateHubChannel { new_channel } => { execute_update_hub_channel(deps, info, new_channel) } - ExecuteMsg::RequestRegisterDenom { token } => { - execute_request_register_denom(deps, info, token) + ExecuteMsg::RequestRegisterDenom { token, timeout } => { + execute_request_register_denom(&mut deps, env, info, token, timeout) } - ExecuteMsg::RequestDeregisterDenom { token } => { - execute_request_deregister_denom(deps, info, token) + ExecuteMsg::RequestDeregisterDenom { token, timeout } => { + execute_request_deregister_denom(&mut deps, env, info, token, timeout) } ExecuteMsg::RequestPoolCreation { pair, + slippage_tolerance_bps, lp_token_name, lp_token_symbol, lp_token_decimal, @@ -142,11 +164,9 @@ pub fn execute( lp_token_symbol, lp_token_decimal, lp_token_marketing, + slippage_tolerance_bps, timeout, ), - ExecuteMsg::RequestRegisterEscrow { token, timeout } => { - execute_request_register_escrow(&mut deps, env, info, token, timeout) - } ExecuteMsg::WithdrawVirtualBalance { token, amount, @@ -161,13 +181,42 @@ pub fn execute( cross_chain_addresses, timeout, ), - + ExecuteMsg::TransferVirtualBalance { + token, + amount, + recipient_address, + timeout, + } => execute_transfer_virtual_balance( + &mut deps, + env, + info, + token, + amount, + recipient_address, + timeout, + ), + ExecuteMsg::UpdateFactoryState { + router_contract, + admin, + escrow_code_id, + cw20_code_id, + is_native, + } => execute_update_state( + deps, + info, + router_contract, + admin, + escrow_code_id, + cw20_code_id, + is_native, + ), ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::EuclidReceive(msg) => receive_euclid_native(deps, env, info, msg), ExecuteMsg::IbcCallbackAckAndTimeout { ack } => { - ibc::ack_and_timeout::ibc_ack_packet_internal_call(deps, env, ack) + ibc::ack_and_timeout::ibc_ack_packet_internal_call(deps, info, env, ack) } ExecuteMsg::IbcCallbackReceive { receive_msg } => { - ibc::receive::ibc_receive_internal_call(deps, env, receive_msg) + ibc::receive::ibc_receive_internal_call(deps, env, info, receive_msg) } ExecuteMsg::NativeReceiveCallback { msg } => { execute_native_receive_callback(deps, env, info, msg) diff --git a/contracts/liquidity/factory/src/execute.rs b/contracts/liquidity/factory/src/execute.rs index 7302688e..7cc5a196 100644 --- a/contracts/liquidity/factory/src/execute.rs +++ b/contracts/liquidity/factory/src/execute.rs @@ -1,32 +1,40 @@ use cosmwasm_std::{ - ensure, from_json, to_json_binary, Binary, CosmosMsg, Decimal, DepsMut, Env, IbcTimeout, - MessageInfo, Response, SubMsg, Uint128, WasmMsg, + ensure, from_json, Binary, CosmosMsg, Decimal, DepsMut, Env, IbcTimeout, MessageInfo, Response, + Uint128, }; -use cw20::Cw20ReceiveMsg; +use cw20::{Cw20ReceiveMsg, Logo}; use euclid::{ chain::{CrossChainUser, CrossChainUserWithLimit}, - cw20::Cw20HookMsg, + deposit::DepositTokenRequest, error::ContractError, - events::{swap_event, tx_event, TxType}, - fee::{PartnerFee, MAX_PARTNER_FEE_BPS}, + events::{deposit_token_event, simple_event, swap_event, tx_event, TxType}, + fee::{PartnerFee, BPS_100_PERCENT, MAX_PARTNER_FEE_BPS}, liquidity::{AddLiquidityRequest, RemoveLiquidityRequest}, - msgs::escrow::{AllowedTokenResponse, QueryMsg as EscrowQueryMsg}, - pool::{EscrowCreateRequest, PoolCreateRequest}, + msgs::{ + escrow::{AllowedTokenResponse, QueryMsg as EscrowQueryMsg}, + factory::{ + cw20::FactoryCw20HookMsg, euclid_receive::FactoryEuclidReceiveHook, ExecuteMsg, + ExecuteSwapRequest, + }, + hook::EuclidReceive, + }, + pool::{DenomRegisterDeregisterRequest, PoolCreateRequest}, swap::{NextSwapPair, SwapRequest}, timeout::get_timeout, - token::{Pair, PairWithDenom, Token, TokenWithDenom}, - utils::generate_tx, + token::{Pair, PairWithDenomAndAmount, Token, TokenType, TokenWithDenom}, + utils::{fund_manager::FundManager, tx::generate_tx}, }; use euclid_ibc::msg::{ - ChainIbcExecuteMsg, ChainIbcRemoveLiquidityExecuteMsg, ChainIbcWithdrawExecuteMsg, - HubIbcExecuteMsg, + ChainIbcExecuteMsg, ChainIbcRemoveLiquidityExecuteMsg, ChainIbcTransferExecuteMsg, + ChainIbcWithdrawExecuteMsg, HubIbcExecuteMsg, }; use crate::{ ibc::receive, state::{ - HUB_CHANNEL, PAIR_TO_VLP, PENDING_ADD_LIQUIDITY, PENDING_ESCROW_REQUESTS, - PENDING_POOL_REQUESTS, PENDING_REMOVE_LIQUIDITY, PENDING_SWAPS, STATE, TOKEN_TO_ESCROW, + State, HUB_CHANNEL, PAIR_TO_VLP, PENDING_ADD_LIQUIDITY, + PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS, PENDING_POOL_REQUESTS, + PENDING_REMOVE_LIQUIDITY, PENDING_SWAPS, PENDING_TOKEN_DEPOSIT, STATE, TOKEN_TO_ESCROW, VLP_TO_CW20, }, }; @@ -55,13 +63,22 @@ pub fn execute_request_pool_creation( deps: &mut DepsMut, env: Env, info: MessageInfo, - pair: PairWithDenom, + pair_with_denom_and_amount: PairWithDenomAndAmount, lp_token_name: String, lp_token_symbol: String, lp_token_decimal: u8, lp_token_marketing: Option, + slippage_tolerance_bps: u64, timeout: Option, ) -> Result { + ensure!( + slippage_tolerance_bps.le(&BPS_100_PERCENT), + ContractError::InvalidSlippageTolerance {} + ); + + let pair = pair_with_denom_and_amount.get_pair()?; + pair.validate()?; + let state = STATE.load(deps.storage)?; let sender = CrossChainUser { address: info.sender.to_string(), @@ -69,35 +86,112 @@ pub fn execute_request_pool_creation( }; let tx_id = generate_tx(deps.branch(), &env, &sender)?; + let mut res = Response::new(); + + // Changes factory state without sending liquidity request to router. That will be handled in Pool creation request's reply in router + // Add liquidity Section // + // Prepare msg vector + let mut msgs: Vec = Vec::new(); + + let mut fund_manager = FundManager::new(&info.funds); + let mut one_token_already_exists = false; + // Do an early check for tokens escrow so that if it exists, it should allow the denom that we are sending + let tokens = pair_with_denom_and_amount.get_vec_token_info(); + for token in tokens { + // Validate token id + token.token.validate()?; + + // Vouchers are not escrowed + if !token.token_type.is_voucher() { + match token.token_type.clone() { + TokenType::Native { denom } => { + // Use funds, if its not present this will throw error. + // This will make sure enough funds are provided with the message + fund_manager.use_fund(token.amount, &denom)?; + } + TokenType::Smart { .. } => { + let msg = token.token_type.create_transfer_msg( + token.amount, + env.contract.address.clone().to_string(), + Some(sender.address.clone()), + None, + )?; + msgs.push(msg); + } + TokenType::Voucher { .. } => return Err(ContractError::UnreachableCode {}), + } + // Ensure valid denom if token already exists + let escrow_address = TOKEN_TO_ESCROW.may_load(deps.storage, token.clone().token)?; + if let Some(escrow_address) = escrow_address { + let token_allowed_query_msg = EscrowQueryMsg::TokenAllowed { + denom: token.clone().token_type, + }; + let token_allowed: AllowedTokenResponse = deps + .querier + .query_wasm_smart(escrow_address.clone(), &token_allowed_query_msg)?; + + ensure!( + token_allowed.allowed, + ContractError::UnsupportedDenomination {} + ); + one_token_already_exists = true; + } + } else { + // If its a voucher token, then we can assume that one token already exists + one_token_already_exists = true; + } + } + + ensure!( + fund_manager.validate_funds_are_empty().is_ok(), + ContractError::new("Extra funds are not allowed") + ); + + ensure!( + one_token_already_exists, + ContractError::new( + "Cannot create pool two new tokens. Atleast one token must already be registered." + ) + ); + + res = res.add_messages(msgs); + + let pair = pair_with_denom_and_amount.get_pair()?; + // Ensure tokens in pair are different + ensure!( + pair.token_1 != pair.token_2, + ContractError::new("Cannot create pool with same token") + ); + ensure!( !PENDING_POOL_REQUESTS.has(deps.storage, (info.sender.clone(), tx_id.clone())), ContractError::TxAlreadyExist {} ); ensure!( - !PAIR_TO_VLP.has(deps.storage, pair.get_pair()?.get_tupple()), + !PAIR_TO_VLP.has(deps.storage, pair.get_tupple()), ContractError::PoolAlreadyExists {} ); - let tokens = pair.get_vec_token_info(); - for token in tokens { - let escrow_address = TOKEN_TO_ESCROW.may_load(deps.storage, token.clone().token)?; - if let Some(escrow_address) = escrow_address { - let token_allowed_query_msg = EscrowQueryMsg::TokenAllowed { - denom: token.clone().token_type, - }; - let token_allowed: AllowedTokenResponse = deps - .querier - .query_wasm_smart(escrow_address.clone(), &token_allowed_query_msg)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; + let timeout = get_timeout(timeout)?; + // We might get errors in ack if marketing is not valid + if let Some(marketing) = &lp_token_marketing { + if let Some(logo) = &marketing.logo { ensure!( - token_allowed.allowed, - ContractError::UnsupportedDenomination {} + matches!(logo, Logo::Url(_)), + ContractError::new("Only URL logos are supported") ); } - } - let channel = HUB_CHANNEL.load(deps.storage)?; - let timeout = get_timeout(timeout)?; + if let Some(marketing_address) = &marketing.marketing { + deps.api.addr_validate(marketing_address)?; + } + } let lp_token_instantiate_msg = cw20_base::msg::InstantiateMsg { name: lp_token_name, @@ -111,19 +205,21 @@ pub fn execute_request_pool_creation( marketing: lp_token_marketing, }; lp_token_instantiate_msg.validate()?; + let req = PoolCreateRequest { tx_id: tx_id.clone(), sender: info.sender.to_string(), - pair_info: pair.clone(), + pair_info: pair_with_denom_and_amount.clone(), lp_token_instantiate_msg, }; PENDING_POOL_REQUESTS.save(deps.storage, (info.sender.clone(), tx_id.clone()), &req)?; let pool_create_msg = ChainIbcExecuteMsg::RequestPoolCreation { - pair: pair.get_pair()?, + pair: pair_with_denom_and_amount, sender, tx_id: tx_id.clone(), + slippage_tolerance_bps, } .to_msg( deps, @@ -135,7 +231,7 @@ pub fn execute_request_pool_creation( timeout, )?; - Ok(Response::new() + Ok(res .add_event(tx_event( &tx_id, info.sender.as_str(), @@ -146,80 +242,16 @@ pub fn execute_request_pool_creation( .add_submessage(pool_create_msg)) } -pub fn execute_request_register_escrow( - deps: &mut DepsMut, - env: Env, - info: MessageInfo, - token: TokenWithDenom, - timeout: Option, -) -> Result { - let state = STATE.load(deps.storage)?; - ensure!(state.admin == info.sender, ContractError::Unauthorized {}); - - let sender = CrossChainUser { - address: info.sender.to_string(), - chain_uid: state.chain_uid.clone(), - }; - let tx_id = generate_tx(deps.branch(), &env, &sender)?; - - ensure!( - !PENDING_ESCROW_REQUESTS.has(deps.storage, (info.sender.clone(), tx_id.clone())), - ContractError::TxAlreadyExist {} - ); - - let escrow_address = TOKEN_TO_ESCROW.has(deps.storage, token.clone().token); - ensure!(!escrow_address, ContractError::TokenAlreadyExist {}); - - let channel = HUB_CHANNEL.load(deps.storage)?; - let timeout = get_timeout(timeout)?; - - let register_escrow_msg = ChainIbcExecuteMsg::RequestEscrowCreation { - token: token.clone().token, - sender, - tx_id: tx_id.clone(), - } - .to_msg( - deps, - &env, - state.router_contract, - state.chain_uid, - state.is_native, - channel, - timeout, - )?; - - let req = EscrowCreateRequest { - tx_id: tx_id.clone(), - sender: info.sender.to_string(), - token, - }; - - PENDING_ESCROW_REQUESTS.save(deps.storage, (info.sender.clone(), tx_id.clone()), &req)?; - - Ok(Response::new() - .add_event(tx_event( - &tx_id, - info.sender.as_str(), - euclid::events::TxType::PoolCreation, - )) - .add_attribute("tx_id", tx_id) - .add_attribute("method", "request_escrow_creation") - .add_submessage(register_escrow_msg)) -} - // Add liquidity to the pool // TODO look into alternatives of using .branch(), maybe unifying the functions would help pub fn add_liquidity_request( deps: &mut DepsMut, info: MessageInfo, env: Env, - pair_info: PairWithDenom, - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - slippage_tolerance: u64, + pair_info: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, timeout: Option, ) -> Result { - pair_info.validate()?; let pair = pair_info.get_pair()?; // Check that slippage tolerance is between 1 and 100 @@ -231,7 +263,7 @@ pub fn add_liquidity_request( let tx_id = generate_tx(deps.branch(), &env, &sender)?; ensure!( - (1..=100).contains(&slippage_tolerance), + (1..=BPS_100_PERCENT).contains(&slippage_tolerance_bps), ContractError::InvalidSlippageTolerance {} ); @@ -241,108 +273,78 @@ pub fn add_liquidity_request( ); ensure!( PAIR_TO_VLP.has(deps.storage, pair.get_tupple()), - ContractError::PoolDoesNotExists {} + ContractError::PoolDoesNotExist {} ); - let channel = HUB_CHANNEL.load(deps.storage)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; let timeout = get_timeout(timeout)?; - // Check that the liquidity is greater than 0 - ensure!( - !(token_1_liquidity.is_zero() || token_2_liquidity.is_zero()), - ContractError::ZeroAssetAmount {} - ); + // Prepare msg vector + let mut msgs: Vec = Vec::new(); + let mut fund_manager = FundManager::new(&info.funds); // Do an early check for tokens escrow so that if it exists, it should allow the denom that we are sending let tokens = pair_info.get_vec_token_info(); for token in tokens { - let escrow_address = TOKEN_TO_ESCROW - .load(deps.storage, token.token) - .or(Err(ContractError::EscrowDoesNotExist {}))?; - let token_allowed_query_msg = EscrowQueryMsg::TokenAllowed { - denom: token.token_type, - }; - let token_allowed: AllowedTokenResponse = deps - .querier - .query_wasm_smart(escrow_address.clone(), &token_allowed_query_msg)?; - - ensure!( - token_allowed.allowed, - ContractError::UnsupportedDenomination {} - ); - } + // validate token + token.token_type.validate(&deps.as_ref())?; - // Get the token 1 and token 2 from the pair info - let token_1 = pair_info.token_1.clone(); - let token_2 = pair_info.token_2.clone(); + // Ensure liquidity is not zero + ensure!(!token.amount.is_zero(), ContractError::ZeroAssetAmount {}); - // Prepare msg vector - let mut msgs: Vec = Vec::new(); - - // IF TOKEN IS A SMART CONTRACT IT REQUIRES APPROVAL FOR TRANSFER - if token_1.token_type.is_smart() { - let msg = token_1.token_type.create_transfer_msg( - token_1_liquidity, - env.contract.address.clone().to_string(), - Some(sender.address.clone()), - )?; - msgs.push(msg); - } else { - // If funds empty return error - ensure!( - !info.funds.is_empty(), - ContractError::InsufficientDeposit {} - ); + // Vouchers are not escrowed + if !token.token_type.is_voucher() { + let escrow_address = TOKEN_TO_ESCROW + .load(deps.storage, token.token) + .or(Err(ContractError::EscrowDoesNotExist {}))?; + let token_allowed_query_msg = EscrowQueryMsg::TokenAllowed { + denom: token.token_type.clone(), + }; + let token_allowed: AllowedTokenResponse = deps + .querier + .query_wasm_smart(escrow_address.clone(), &token_allowed_query_msg)?; - // Check for funds sent with the message - let amt = info - .funds - .iter() - .find(|x| x.denom == token_1.token_type.get_denom()) - .ok_or(ContractError::Generic { - err: "Denom not found".to_string(), - })?; + ensure!( + token_allowed.allowed, + ContractError::UnsupportedDenomination {} + ); - ensure!( - amt.amount.ge(&token_1_liquidity), - ContractError::InsufficientDeposit {} - ); + match token.token_type { + TokenType::Native { denom } => { + ensure!( + !info.funds.is_empty(), + ContractError::InsufficientDeposit {} + ); + // Use funds, if its not present this will throw error. + // This will make sure enough funds are provided with the message + fund_manager.use_fund(token.amount, &denom)?; + } + TokenType::Smart { .. } => { + let msg = token.token_type.create_transfer_msg( + token.amount, + env.contract.address.clone().to_string(), + Some(sender.address.clone()), + None, + )?; + msgs.push(msg); + } + TokenType::Voucher { .. } => return Err(ContractError::UnreachableCode {}), + } + } } - // Same for token 2 - if token_2.token_type.is_smart() { - let msg = token_2.token_type.create_transfer_msg( - token_2_liquidity, - env.contract.address.clone().to_string(), - Some(sender.address.clone()), - )?; - msgs.push(msg); - } else { - // If funds empty return error - ensure!( - !info.funds.is_empty(), - ContractError::InsufficientDeposit {} - ); - - let amt = info - .funds - .iter() - .find(|x| x.denom == token_2.token_type.get_denom()) - .ok_or(ContractError::Generic { - err: "Denom not found".to_string(), - })?; - - ensure!( - amt.amount.ge(&token_2_liquidity), - ContractError::InsufficientDeposit {} - ); - } + ensure!( + fund_manager.validate_funds_are_empty().is_ok(), + ContractError::new("Extra funds are not allowed") + ); let liquidity_tx_info = AddLiquidityRequest { sender: info.sender.to_string(), - token_1_liquidity, - token_2_liquidity, - pair_info, + pair_info: pair_info.clone(), tx_id: tx_id.clone(), }; @@ -354,10 +356,8 @@ pub fn add_liquidity_request( let add_liq_msg = ChainIbcExecuteMsg::AddLiquidity { sender, - token_1_liquidity, - token_2_liquidity, - slippage_tolerance, - pair, + slippage_tolerance_bps, + pair: pair_info, tx_id: tx_id.clone(), } .to_msg( @@ -411,11 +411,15 @@ pub fn remove_liquidity_request( ensure!( PAIR_TO_VLP.has(deps.storage, pair.get_tupple()), - ContractError::PoolDoesNotExists {} + ContractError::PoolDoesNotExist {} ); // TODO: Do we want to add check for lp shares for early fail? - let channel = HUB_CHANNEL.load(deps.storage)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; let timeout = get_timeout(timeout)?; // Check that the liquidity is greater than 0 @@ -467,24 +471,25 @@ pub fn remove_liquidity_request( pub fn execute_swap_request( deps: &mut DepsMut, - info: MessageInfo, env: Env, + info: MessageInfo, sender: CrossChainUser, asset_in: TokenWithDenom, - asset_out: Token, amount_in: Uint128, + asset_out: Token, min_amount_out: Uint128, swaps: Vec, timeout: Option, cross_chain_addresses: Vec, partner_fee: Option, ) -> Result { + // Validate asset in + asset_in.token_type.validate(&deps.as_ref())?; + asset_in.token.validate()?; + let state = STATE.load(deps.storage)?; let sender_addr = deps.api.addr_validate(&sender.address)?; - // Validate asset in - asset_in.validate(deps.as_ref())?; - let tx_id = generate_tx(deps.branch(), &env, &sender)?; let partner_fee_bps = partner_fee @@ -497,6 +502,42 @@ pub fn execute_swap_request( ContractError::InvalidPartnerFee {} ); + if !asset_in.token_type.is_voucher() { + // Verify that this asset is allowed + let escrow = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; + + let token_allowed: euclid::msgs::escrow::AllowedTokenResponse = + deps.querier.query_wasm_smart( + escrow, + &euclid::msgs::escrow::QueryMsg::TokenAllowed { + denom: asset_in.token_type.clone(), + }, + )?; + ensure!( + token_allowed.allowed, + ContractError::UnsupportedDenomination {} + ); + } + + let mut fund_manager = FundManager::new(&info.funds); + match &asset_in.token_type { + TokenType::Native { denom } => { + // Verify thatthe amount of funds passed is greater than the asset amount + fund_manager.use_fund(amount_in, denom)?; + } + TokenType::Smart { contract_address } => { + ensure!( + info.sender == *contract_address, + ContractError::Unauthorized {} + ); + } + TokenType::Voucher { .. } => {} + } + ensure!( + fund_manager.validate_funds_are_empty().is_ok(), + ContractError::new("Extra funds sent with message") + ); + let partner_fee_amount = amount_in.checked_mul_ceil(Decimal::bps(partner_fee_bps))?; let amount_in = amount_in.checked_sub(partner_fee_amount)?; @@ -529,65 +570,31 @@ pub fn execute_swap_request( ContractError::new("Amount out doesn't match swap route") ); - let channel = HUB_CHANNEL.load(deps.storage)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; let timeout = get_timeout(timeout)?; - // Verify that this asset is allowed - let escrow = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; - - let token_allowed: euclid::msgs::escrow::AllowedTokenResponse = deps.querier.query_wasm_smart( - escrow, - &euclid::msgs::escrow::QueryMsg::TokenAllowed { - denom: asset_in.token_type.clone(), - }, - )?; - - ensure!( - token_allowed.allowed, - ContractError::UnsupportedDenomination {} - ); + let partner_fee_recipient = partner_fee + .clone() + .map(|partner_fee| deps.api.addr_validate(&partner_fee.recipient)) + .transpose()? + .unwrap_or(sender_addr.clone()); - // Verify if the token is native - if asset_in.token_type.is_native() { - // Get the denom of native token - let denom = asset_in.token_type.get_denom(); - - // Verify thatthe amount of funds passed is greater than the asset amount - if info - .funds - .iter() - .find(|x| x.denom == denom) - .ok_or(ContractError::Generic { - err: "Denom not found".to_string(), - })? - .amount - < amount_in - { - return Err(ContractError::Generic { - err: "Funds attached are less than funds needed".to_string(), - }); - } - } else { - // Verify that the contract address is the same as the asset contract address - ensure!( - info.sender == asset_in.token_type.get_denom(), - ContractError::Unauthorized {} - ); - } let swap_info = SwapRequest { sender: sender_addr.to_string(), asset_in: asset_in.clone(), - asset_out: asset_out.clone(), amount_in, + asset_out: asset_out.clone(), min_amount_out, swaps: swaps.clone(), timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), tx_id: tx_id.clone(), cross_chain_addresses: cross_chain_addresses.clone(), partner_fee_amount, - partner_fee_recipient: partner_fee - .map(|partner_fee| deps.api.addr_validate(&partner_fee.recipient)) - .transpose()?, + partner_fee_recipient: partner_fee_recipient.clone(), }; PENDING_SWAPS.save( deps.storage, @@ -597,19 +604,24 @@ pub fn execute_swap_request( let swap_msg = ChainIbcExecuteMsg::Swap(euclid_ibc::msg::ChainIbcSwapExecuteMsg { sender, - asset_in: asset_in.token, + asset_in, amount_in, asset_out, min_amount_out, swaps, tx_id: tx_id.clone(), cross_chain_addresses, + partner_fee_recipient: CrossChainUser { + address: partner_fee_recipient.to_string(), + chain_uid: state.chain_uid.clone(), + }, + partner_fee_amount, }) .to_msg( deps, &env, - state.clone().router_contract, - state.clone().chain_uid, + state.router_contract.clone(), + state.chain_uid.clone(), state.is_native, channel, timeout, @@ -627,6 +639,121 @@ pub fn execute_swap_request( .add_submessage(swap_msg)) } +pub fn execute_deposit_token( + deps: &mut DepsMut, + env: Env, + info: MessageInfo, + sender: CrossChainUser, + asset_in: TokenWithDenom, + amount_in: Uint128, + timeout: Option, + recipient: Option, +) -> Result { + ensure!( + !asset_in.token_type.is_voucher(), + ContractError::UnsupportedDenomination {} + ); + + let state = STATE.load(deps.storage)?; + + let sender_addr = deps.api.addr_validate(&sender.address)?; + let recipient = recipient.unwrap_or(sender.clone()); + + // Validate asset in + asset_in.token.validate()?; + asset_in.token_type.validate(&deps.as_ref())?; + + let tx_id = generate_tx(deps.branch(), &env, &sender)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; + + let timeout = get_timeout(timeout)?; + + ensure!( + !PENDING_TOKEN_DEPOSIT.has(deps.storage, (sender_addr.clone(), tx_id.clone())), + ContractError::TxAlreadyExist {} + ); + // Verify that this asset is allowed + let escrow = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; + + let token_allowed: euclid::msgs::escrow::AllowedTokenResponse = deps.querier.query_wasm_smart( + escrow, + &euclid::msgs::escrow::QueryMsg::TokenAllowed { + denom: asset_in.token_type.clone(), + }, + )?; + ensure!( + token_allowed.allowed, + ContractError::UnsupportedDenomination {} + ); + let mut fund_manager = FundManager::new(&info.funds); + + match &asset_in.token_type { + TokenType::Native { denom } => { + fund_manager.use_fund(amount_in, denom)?; + } + TokenType::Smart { contract_address } => { + ensure!( + info.sender == *contract_address, + ContractError::Unauthorized {} + ); + } + TokenType::Voucher { .. } => {} + } + + ensure!( + fund_manager.validate_funds_are_empty().is_ok(), + ContractError::new("Extra funds sent with message") + ); + + let deposit_token_info = DepositTokenRequest { + sender: sender_addr.to_string(), + asset_in: asset_in.clone(), + amount_in, + timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(timeout)), + tx_id: tx_id.clone(), + recipient: recipient.clone(), + }; + + PENDING_TOKEN_DEPOSIT.save( + deps.storage, + (sender_addr.clone(), tx_id.clone()), + &deposit_token_info, + )?; + + let deposit_token_msg = + ChainIbcExecuteMsg::DepositToken(euclid_ibc::msg::ChainIbcDepositTokenExecuteMsg { + sender, + asset_in: asset_in.token, + amount_in, + tx_id: tx_id.clone(), + recipient, + }) + .to_msg( + deps, + &env, + state.clone().router_contract, + state.clone().chain_uid, + state.is_native, + channel, + timeout, + )?; + + Ok(Response::new() + .add_event(tx_event( + &tx_id, + sender_addr.as_str(), + euclid::events::TxType::DepositToken, + )) + .add_event(deposit_token_event(&tx_id, &deposit_token_info)) + .add_attribute("tx_id", tx_id) + .add_attribute("method", "execute_deposit_token") + .add_submessage(deposit_token_msg)) +} + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. /// /// * **cw20_msg** is the CW20 message that has to be processed. @@ -645,7 +772,7 @@ pub fn receive_cw20( match from_json(&cw20_msg.msg)? { // Allow to swap using a CW20 hook message - Cw20HookMsg::Swap { + FactoryCw20HookMsg::Swap { asset_in, asset_out, min_amount_out, @@ -658,7 +785,7 @@ pub fn receive_cw20( // ensure that contract address is same as asset being swapped ensure!( - contract_adr == asset_in.get_denom(), + contract_adr == asset_in.token_type.get_smart_contract_address()?, ContractError::AssetDoesNotExist {} ); @@ -667,12 +794,12 @@ pub fn receive_cw20( // ensure that the contract address is the same as the asset contract address execute_swap_request( &mut deps, - info, env, + info, sender, asset_in, - asset_out, amount_in, + asset_out, min_amount_out, swaps, timeout, @@ -680,7 +807,7 @@ pub fn receive_cw20( partner_fee, ) } - Cw20HookMsg::RemoveLiquidity { + FactoryCw20HookMsg::RemoveLiquidity { pair, lp_allocation, timeout, @@ -695,68 +822,295 @@ pub fn receive_cw20( timeout, cross_chain_addresses, ), + FactoryCw20HookMsg::Deposit { + token, + recipient, + timeout, + } => { + let contract_adr = info.sender.clone(); - _ => Err(ContractError::NotImplemented {}), + let asset_in = token.with_type(TokenType::Smart { + contract_address: contract_adr.to_string(), + }); + let amount_in = cw20_msg.amount; + + // ensure that the contract address is the same as the asset contract address + execute_deposit_token( + &mut deps, env, info, sender, asset_in, amount_in, timeout, recipient, + ) + } + FactoryCw20HookMsg::EuclidReceive(euclid_receive) => { + receive_euclid_cw20(deps, env, info, sender, cw20_msg.amount, euclid_receive) + } } } -// New factory functions // -pub fn execute_request_register_denom( +pub fn receive_euclid_native( deps: DepsMut, + env: Env, + info: MessageInfo, + euclid_receive: EuclidReceive, +) -> Result { + match from_json::(euclid_receive.data.clone())? { + FactoryEuclidReceiveHook::Swap { + sender, + asset_in, + asset_out, + min_amount_out, + swaps, + timeout, + cross_chain_addresses, + partner_fee, + } => { + ensure!( + asset_in.token_type.is_native(), + ContractError::InvalidAsset { + asset: asset_in.token.to_string(), + } + ); + let swap_msg = ExecuteSwapRequest { + sender: sender.clone(), + asset_in, + amount_in: Uint128::zero(), + asset_out, + min_amount_out, + swaps, + timeout, + cross_chain_addresses, + partner_fee, + }; + let response = crate::contract::execute( + deps, + env, + info, + ExecuteMsg::ExecuteSwapRequest(swap_msg), + )?; + let event = simple_event() + .add_attribute("meta", euclid_receive.meta.unwrap_or("no_meta".to_string())); + Ok(response.add_event(event)) + } + } +} + +pub fn receive_euclid_cw20( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + sender: CrossChainUser, + amount: Uint128, + euclid_msg: EuclidReceive, +) -> Result { + match from_json::(euclid_msg.data.clone())? { + FactoryEuclidReceiveHook::Swap { + sender: _sender, + asset_in, + asset_out, + min_amount_out, + swaps, + timeout, + cross_chain_addresses, + partner_fee, + } => { + ensure!( + info.sender == asset_in.token_type.get_smart_contract_address()?, + ContractError::Unauthorized {} + ); + let response = execute_swap_request( + &mut deps, + env, + info, + _sender.unwrap_or(sender), + asset_in, + amount, + asset_out, + min_amount_out, + swaps, + timeout, + cross_chain_addresses, + partner_fee, + )?; + let event = simple_event().add_attribute( + "meta", + euclid_msg.meta.clone().unwrap_or("no_meta".to_string()), + ); + Ok(response.add_event(event)) + } + } +} + +pub fn execute_request_register_denom( + deps: &mut DepsMut, + env: Env, info: MessageInfo, token: TokenWithDenom, + timeout: Option, ) -> Result { - let admin = STATE.load(deps.storage)?.admin; + // Vouchers are not registered ensure!( - admin == info.sender.into_string(), - ContractError::Unauthorized {} + !token.token_type.is_voucher(), + ContractError::UnsupportedDenomination {} ); - let escrow_address = TOKEN_TO_ESCROW - .load(deps.storage, token.token.clone()) - .map_err(|_err| ContractError::EscrowDoesNotExist {})?; + let state = STATE.load(deps.storage)?; + ensure!(state.admin == info.sender, ContractError::Unauthorized {}); - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: escrow_address.into_string(), - msg: to_json_binary(&euclid::msgs::escrow::ExecuteMsg::AddAllowedDenom { + let sender = CrossChainUser { + address: info.sender.to_string(), + chain_uid: state.chain_uid.clone(), + }; + let tx_id = generate_tx(deps.branch(), &env, &sender)?; + + ensure!( + !PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS + .has(deps.storage, (info.sender.clone(), tx_id.clone())), + ContractError::TxAlreadyExist {} + ); + let escrow_address = TOKEN_TO_ESCROW.may_load(deps.storage, token.token.clone())?; + if let Some(escrow_address) = escrow_address { + let denom_allowed_msg = EscrowQueryMsg::TokenAllowed { denom: token.token_type.clone(), - })?, - funds: vec![], - }); + }; + let denom_allowed: AllowedTokenResponse = deps + .querier + .query_wasm_smart(escrow_address, &denom_allowed_msg)?; + + // Denom should not be already registered + ensure!( + !denom_allowed.allowed, + ContractError::EscrowAlreadyExists {} + ); + } + + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; + let timeout = get_timeout(timeout)?; + + let request_register_denom_msg = ChainIbcExecuteMsg::RegisterDenom { + token: token.clone(), + sender, + tx_id: tx_id.clone(), + } + .to_msg( + deps, + &env, + state.router_contract, + state.chain_uid, + state.is_native, + channel, + timeout, + )?; + + let req = DenomRegisterDeregisterRequest { + tx_id: tx_id.clone(), + sender: info.sender.to_string(), + token: token.clone(), + }; + + PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS.save( + deps.storage, + (info.sender.clone(), tx_id.clone()), + &req, + )?; + Ok(Response::new() - .add_submessage(SubMsg::new(msg)) - .add_attribute("method", "request_add_allowed_denom") + .add_event(tx_event( + &tx_id, + info.sender.as_str(), + euclid::events::TxType::PoolCreation, + )) + .add_attribute("tx_id", tx_id) + .add_attribute("method", "request_register_denom") .add_attribute("token", token.token.to_string()) - .add_attribute("denom", token.token_type.get_key())) + .add_attribute("token_type", token.token_type.get_key()) + .add_submessage(request_register_denom_msg)) } pub fn execute_request_deregister_denom( - deps: DepsMut, + deps: &mut DepsMut, + env: Env, info: MessageInfo, token: TokenWithDenom, + timeout: Option, ) -> Result { - let admin = STATE.load(deps.storage)?.admin; + // Vouchers are not registered ensure!( - admin == info.sender.into_string(), - ContractError::Unauthorized {} + !token.token_type.is_voucher(), + ContractError::UnsupportedDenomination {} ); - let escrow_address = TOKEN_TO_ESCROW - .load(deps.storage, token.token.clone()) - .map_err(|_err| ContractError::EscrowDoesNotExist {})?; + let state = STATE.load(deps.storage)?; + ensure!(state.admin == info.sender, ContractError::Unauthorized {}); + + let sender = CrossChainUser { + address: info.sender.to_string(), + chain_uid: state.chain_uid.clone(), + }; + let tx_id = generate_tx(deps.branch(), &env, &sender)?; + + ensure!( + !PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS + .has(deps.storage, (info.sender.clone(), tx_id.clone())), + ContractError::TxAlreadyExist {} + ); + let escrow_address = TOKEN_TO_ESCROW.load(deps.storage, token.token.clone())?; + let denom_allowed_msg = EscrowQueryMsg::TokenAllowed { + denom: token.token_type.clone(), + }; + let denom_allowed: AllowedTokenResponse = deps + .querier + .query_wasm_smart(escrow_address, &denom_allowed_msg)?; + + // Denom should be allowed for it to be available for deregister + ensure!(denom_allowed.allowed, ContractError::AssetDoesNotExist {}); + + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; + let timeout = get_timeout(timeout)?; + + let request_deregister_denom_msg = ChainIbcExecuteMsg::DeRegisterDenom { + token: token.clone(), + sender, + tx_id: tx_id.clone(), + } + .to_msg( + deps, + &env, + state.router_contract, + state.chain_uid, + state.is_native, + channel, + timeout, + )?; + + let req = DenomRegisterDeregisterRequest { + tx_id: tx_id.clone(), + sender: info.sender.to_string(), + token: token.clone(), + }; + + PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS.save( + deps.storage, + (info.sender.clone(), tx_id.clone()), + &req, + )?; - let msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: escrow_address.into_string(), - msg: to_json_binary(&euclid::msgs::escrow::ExecuteMsg::DisallowDenom { - denom: token.token_type.clone(), - })?, - funds: vec![], - }); Ok(Response::new() - .add_submessage(SubMsg::new(msg)) - .add_attribute("method", "request_disallow_denom") + .add_event(tx_event( + &tx_id, + info.sender.as_str(), + euclid::events::TxType::PoolCreation, + )) + .add_attribute("tx_id", tx_id) + .add_attribute("method", "request_deregister_denom") .add_attribute("token", token.token.to_string()) - .add_attribute("denom", token.token_type.get_key())) + .add_attribute("token_type", token.token_type.get_key()) + .add_submessage(request_deregister_denom_msg)) } pub fn execute_withdraw_virtual_balance( @@ -770,7 +1124,11 @@ pub fn execute_withdraw_virtual_balance( ) -> Result { let state = STATE.load(deps.storage)?; - let channel = HUB_CHANNEL.load(deps.storage)?; + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; let sender = CrossChainUser { address: info.sender.to_string(), chain_uid: state.chain_uid.clone(), @@ -807,6 +1165,112 @@ pub fn execute_withdraw_virtual_balance( .add_submessage(withdraw_msg)) } +pub fn execute_transfer_virtual_balance( + deps: &mut DepsMut, + env: Env, + info: MessageInfo, + token: Token, + amount: Uint128, + recipient_address: CrossChainUser, + timeout: Option, +) -> Result { + // The transfer amount should be greater than zero + ensure!(!amount.is_zero(), ContractError::ZeroAssetAmount {}); + + // Validate recipient address + recipient_address.validate()?; + + let state = STATE.load(deps.storage)?; + + let channel = if !state.is_native { + HUB_CHANNEL.load(deps.storage)? + } else { + String::default() + }; + let sender = CrossChainUser { + address: info.sender.to_string(), + chain_uid: state.chain_uid.clone(), + }; + let tx_id = generate_tx(deps.branch(), &env, &sender)?; + let timeout = get_timeout(timeout)?; + + let withdraw_msg = ChainIbcExecuteMsg::Transfer(ChainIbcTransferExecuteMsg { + sender, + token, + amount, + recipient_address, + tx_id: tx_id.clone(), + timeout: Some(timeout), + }) + .to_msg( + deps, + &env, + state.router_contract, + state.chain_uid, + state.is_native, + channel, + timeout, + )?; + + Ok(Response::new() + .add_event(tx_event( + &tx_id, + info.sender.as_str(), + TxType::TransferVirtualBalance, + )) + .add_attribute("tx_id", tx_id) + .add_attribute("method", "withdraw_virtual_balance") + .add_submessage(withdraw_msg)) +} +pub fn execute_update_state( + deps: DepsMut, + info: MessageInfo, + router_contract: Option, + admin: Option, + escrow_code_id: Option, + cw20_code_id: Option, + is_native: Option, +) -> Result { + let state = STATE.load(deps.storage)?; + + ensure!( + state.admin == info.sender.into_string(), + ContractError::Unauthorized {} + ); + + let new_state = State { + router_contract: router_contract.clone().unwrap_or(state.router_contract), + admin: admin.clone().unwrap_or(state.admin), + escrow_code_id: escrow_code_id.unwrap_or(state.escrow_code_id), + cw20_code_id: cw20_code_id.unwrap_or(state.cw20_code_id), + chain_uid: state.chain_uid, + is_native: is_native.unwrap_or(state.is_native), + partner_fees_collected: state.partner_fees_collected, + }; + + STATE.save(deps.storage, &new_state)?; + + Ok(Response::new() + .add_attribute("method", "update_state") + .add_attribute("admin", admin.unwrap_or("unchanged".to_string())) + .add_attribute( + "router_contract", + router_contract.unwrap_or("unchanged".to_string()), + ) + .add_attribute( + "escrow_code_id", + escrow_code_id.unwrap_or(state.escrow_code_id).to_string(), + ) + .add_attribute( + "cw20_code_id", + cw20_code_id.map_or_else(|| "unchanged".to_string(), |x| x.to_string()), + ) + .add_attribute( + "is_native", + is_native.map_or_else(|| "unchanged".to_string(), |x| x.to_string()), + )) +} + pub fn execute_native_receive_callback( deps: DepsMut, env: Env, diff --git a/contracts/liquidity/factory/src/ibc/ack_and_timeout.rs b/contracts/liquidity/factory/src/ibc/ack_and_timeout.rs index 98c2aa20..5b47d031 100644 --- a/contracts/liquidity/factory/src/ibc/ack_and_timeout.rs +++ b/contracts/liquidity/factory/src/ibc/ack_and_timeout.rs @@ -1,20 +1,22 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_json, to_json_binary, Binary, CosmosMsg, DepsMut, Env, IbcAcknowledgement, - IbcBasicResponse, IbcPacketAckMsg, IbcPacketTimeoutMsg, ReplyOn, Response, StdError, StdResult, - SubMsg, Uint128, WasmMsg, + ensure, from_json, to_json_binary, Binary, CosmosMsg, DepsMut, Env, IbcAcknowledgement, + IbcBasicResponse, IbcPacketAckMsg, IbcPacketTimeoutMsg, Int256, MessageInfo, ReplyOn, Response, + StdError, StdResult, SubMsg, WasmMsg, }; +use cw20::Cw20Coin; use euclid::{ + deposit::DepositTokenResponse, error::ContractError, - events::swap_event, + events::{deposit_token_event, swap_event}, liquidity::{AddLiquidityResponse, RemoveLiquidityResponse}, msgs::{ cw20::ExecuteMsg as Cw20ExecuteMsg, escrow::InstantiateMsg as EscrowInstantiateMsg, factory::ExecuteMsg, }, - pool::{EscrowCreationResponse, PoolCreationResponse}, - swap::{SwapResponse, WithdrawResponse}, + pool::{DeRegisterDenomResponse, PoolCreationResponse, RegisterDenomResponse}, + swap::{SwapResponse, TransferResponse, WithdrawResponse}, token::Token, }; use euclid_ibc::{ack::AcknowledgementMsg, msg::ChainIbcExecuteMsg}; @@ -22,9 +24,9 @@ use euclid_ibc::{ack::AcknowledgementMsg, msg::ChainIbcExecuteMsg}; use crate::{ reply::{CW20_INSTANTIATE_REPLY_ID, ESCROW_INSTANTIATE_REPLY_ID, IBC_ACK_AND_TIMEOUT_REPLY_ID}, state::{ - PAIR_TO_VLP, PENDING_ADD_LIQUIDITY, PENDING_ESCROW_REQUESTS, PENDING_POOL_REQUESTS, - PENDING_REMOVE_LIQUIDITY, PENDING_SWAPS, STATE, TOKEN_TO_ESCROW, VLP_TO_CW20, - VLP_TO_LP_SHARES, + PAIR_TO_VLP, PENDING_ADD_LIQUIDITY, PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS, + PENDING_DEPOSIT_TOKEN, PENDING_POOL_REQUESTS, PENDING_REMOVE_LIQUIDITY, PENDING_SWAPS, + PENDING_TOKEN_DEPOSIT, STATE, TOKEN_TO_ESCROW, VLP_TO_CW20, VLP_TO_LP_SHARES, }, }; @@ -56,9 +58,14 @@ pub fn ibc_packet_ack( pub fn ibc_ack_packet_internal_call( deps: DepsMut, + info: MessageInfo, env: Env, ack: IbcPacketAckMsg, ) -> Result { + ensure!( + info.sender == env.contract.address, + ContractError::Unauthorized {} + ); let msg: ChainIbcExecuteMsg = from_json(&ack.original_packet.data)?; reusable_internal_ack_call(deps, env, msg, ack.acknowledgement.data, false) } @@ -78,11 +85,17 @@ pub fn reusable_internal_ack_call( ack_pool_creation(deps, env, sender.address, res, tx_id, is_native) } - ChainIbcExecuteMsg::RequestEscrowCreation { tx_id, sender, .. } => { + ChainIbcExecuteMsg::RegisterDenom { tx_id, sender, .. } => { // Process acknowledgment for pool creation - let res: AcknowledgementMsg = from_json(ack)?; + let res: AcknowledgementMsg = from_json(ack)?; - ack_escrow_creation(deps, env, sender.address, res, tx_id, is_native) + ack_register_denom(deps, env, sender.address, res, tx_id, is_native) + } + ChainIbcExecuteMsg::DeRegisterDenom { tx_id, sender, .. } => { + // Process acknowledgment for pool creation + let res: AcknowledgementMsg = from_json(ack)?; + + ack_deregister_denom(deps, env, sender.address, res, tx_id, is_native) } ChainIbcExecuteMsg::AddLiquidity { tx_id, sender, .. } => { @@ -110,6 +123,22 @@ pub fn reusable_internal_ack_call( msg.tx_id, is_native, ) + } + ChainIbcExecuteMsg::Transfer(msg) => { + let res: AcknowledgementMsg = from_json(ack)?; + ack_transfer_request( + deps, + res, + msg.sender.address, + msg.token, + msg.tx_id, + is_native, + ) + } + ChainIbcExecuteMsg::DepositToken(deposit) => { + // Process acknowledgment for deposit + let res: AcknowledgementMsg = from_json(ack)?; + ack_deposit_token_request(deps, res, deposit.sender.address, deposit.tx_id, is_native) } // ChainIbcExecuteMsg::RequestWithdraw { // token_id, tx_id, .. // } => { @@ -190,28 +219,57 @@ fn ack_pool_creation( // Collects PairInfo into a vector of Token Info for easy iteration let tokens = existing_req.pair_info.get_vec_token_info(); for token in tokens { + if token.token_type.is_voucher() { + continue; + } let escrow_contract = TOKEN_TO_ESCROW.may_load(deps.storage, token.token.clone())?; // Instantiate escrow if one doesn't exist - if escrow_contract.is_none() { - let init_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { - admin: Some(state.admin.clone()), - code_id: escrow_code_id, - msg: to_json_binary(&EscrowInstantiateMsg { - token_id: token.token, - allowed_denom: Some(token.token_type), - })?, - funds: vec![], - label: "escrow".to_string(), - }); - - res = res.add_submessage(SubMsg { - id: ESCROW_INSTANTIATE_REPLY_ID, - msg: init_msg, - gas_limit: None, - reply_on: ReplyOn::Always, - }); + // if escrow_contract.is_none() { + // let init_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { + // admin: Some(state.admin.clone()), + // code_id: escrow_code_id, + // msg: to_json_binary(&EscrowInstantiateMsg { + // token_id: token.token, + // allowed_denom: Some(token.token_type), + // })?, + // funds: vec![], + // label: "escrow".to_string(), + // }); + + // res = res.add_submessage(SubMsg { + // id: ESCROW_INSTANTIATE_REPLY_ID, + // msg: init_msg, + // gas_limit: None, + // reply_on: ReplyOn::Always, + // }); + // } + match escrow_contract { + Some(address) => { + let send_msg = token.token_type.create_escrow_msg(token.amount, address)?; + res = res.add_message(send_msg); + } + // Instantiate escrow if one doesn't exist + None => { + let init_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { + admin: Some(state.admin.clone()), + code_id: escrow_code_id, + msg: to_json_binary(&EscrowInstantiateMsg { + token_id: token.clone().token, + allowed_denom: Some(token.clone().token_type), + })?, + funds: vec![], + label: "escrow".to_string(), + }); + PENDING_DEPOSIT_TOKEN.save(deps.storage, token.clone().token, &token)?; + res = res.add_submessage(SubMsg { + id: ESCROW_INSTANTIATE_REPLY_ID, + msg: init_msg, + gas_limit: None, + reply_on: ReplyOn::Always, + }); + } } } let lp_token_instantiate_data = existing_req.lp_token_instantiate_msg; @@ -223,7 +281,10 @@ fn ack_pool_creation( name: lp_token_instantiate_data.name, symbol: lp_token_instantiate_data.symbol, decimals: lp_token_instantiate_data.decimals, - initial_balances: vec![], + initial_balances: vec![Cw20Coin { + amount: data.mint_lp_tokens, + address: data.sender.address, + }], mint: lp_token_instantiate_data.mint, marketing: lp_token_instantiate_data.marketing, vlp: data.vlp_contract, @@ -254,22 +315,22 @@ fn ack_pool_creation( } } -fn ack_escrow_creation( +fn ack_register_denom( deps: DepsMut, _env: Env, sender: String, - res: AcknowledgementMsg, + res: AcknowledgementMsg, tx_id: String, is_native: bool, ) -> Result { let sender = deps.api.addr_validate(&sender)?; let req_key = (sender, tx_id.clone()); - let existing_req = PENDING_ESCROW_REQUESTS + let existing_req = PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS .may_load(deps.storage, req_key.clone())? .ok_or(ContractError::PoolRequestDoesNotExists { req: tx_id.clone() })?; // Remove pool request from MAP - PENDING_ESCROW_REQUESTS.remove(deps.storage, req_key); + PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS.remove(deps.storage, req_key); // Check whether res is an error or not match res { @@ -278,27 +339,100 @@ fn ack_escrow_creation( let escrow_code_id = state.escrow_code_id; let token = existing_req.token; - // Instantiate escrow - let init_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { - admin: Some(state.admin.clone()), - code_id: escrow_code_id, - msg: to_json_binary(&EscrowInstantiateMsg { - token_id: token.token, - allowed_denom: Some(token.token_type), + let existing_escrow = TOKEN_TO_ESCROW.may_load(deps.storage, token.token.clone())?; + + let mut response = Response::new() + .add_attribute("tx_id", tx_id) + .add_attribute("method", "ack_register_denom") + .add_attribute("token", token.token.to_string()) + .add_attribute("token_type", token.token_type.get_key()); + + if let Some(escrow_address) = existing_escrow { + let msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: escrow_address.into_string(), + msg: to_json_binary(&euclid::msgs::escrow::ExecuteMsg::AddAllowedDenom { + denom: token.token_type.clone(), + })?, + funds: vec![], + }); + response = response + .add_attribute("create_escrow", "false") + .add_message(msg); + } else { + // Instantiate escrow + let init_msg = CosmosMsg::Wasm(WasmMsg::Instantiate { + admin: Some(state.admin.clone()), + code_id: escrow_code_id, + msg: to_json_binary(&EscrowInstantiateMsg { + token_id: token.token, + allowed_denom: Some(token.token_type), + })?, + funds: vec![], + label: "escrow".to_string(), + }); + response = response + .add_attribute("create_escrow", "true") + .add_submessage(SubMsg { + id: ESCROW_INSTANTIATE_REPLY_ID, + msg: init_msg, + gas_limit: None, + reply_on: ReplyOn::Always, + }); + } + + Ok(response) + } + + AcknowledgementMsg::Error(err) => { + if is_native { + return Err(ContractError::new(&err)); + } + Ok(Response::new() + .add_attribute("tx_id", tx_id) + .add_attribute("method", "reject_denom_register") + .add_attribute("error", err.clone())) + } + } +} + +fn ack_deregister_denom( + deps: DepsMut, + _env: Env, + sender: String, + res: AcknowledgementMsg, + tx_id: String, + is_native: bool, +) -> Result { + let sender = deps.api.addr_validate(&sender)?; + let req_key = (sender, tx_id.clone()); + let existing_req = PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS + .may_load(deps.storage, req_key.clone())? + .ok_or(ContractError::PoolRequestDoesNotExists { req: tx_id.clone() })?; + + // Remove pool request from MAP + PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS.remove(deps.storage, req_key); + + // Check whether res is an error or not + match res { + AcknowledgementMsg::Ok(_data) => { + let token = existing_req.token; + + let escrow_address = TOKEN_TO_ESCROW.load(deps.storage, token.token.clone())?; + + let msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: escrow_address.into_string(), + msg: to_json_binary(&euclid::msgs::escrow::ExecuteMsg::DisallowDenom { + denom: token.token_type.clone(), })?, funds: vec![], - label: "escrow".to_string(), }); Ok(Response::new() - .add_submessage(SubMsg { - id: ESCROW_INSTANTIATE_REPLY_ID, - msg: init_msg, - gas_limit: None, - reply_on: ReplyOn::Always, - }) + .add_message(msg) .add_attribute("tx_id", tx_id) - .add_attribute("method", "escrow_creation")) + .add_attribute("method", "ack_deregister_denom") + .add_attribute("token", token.token.to_string()) + .add_attribute("token_type", token.token_type.get_key())) } AcknowledgementMsg::Error(err) => { @@ -307,7 +441,7 @@ fn ack_escrow_creation( } Ok(Response::new() .add_attribute("tx_id", tx_id) - .add_attribute("method", "reject_pool_request") + .add_attribute("method", "reject_denom_deregister") .add_attribute("error", err.clone())) } } @@ -333,27 +467,28 @@ fn ack_add_liquidity( // Remove liquidity shares let shares = VLP_TO_LP_SHARES .may_load(deps.storage, data.vlp_address.clone())? - .unwrap_or(Uint128::zero()); - let shares = shares.checked_add(data.mint_lp_tokens)?; + .unwrap_or(Int256::zero()); + let shares = shares.checked_add(data.mint_lp_tokens.into())?; VLP_TO_LP_SHARES.save(deps.storage, data.vlp_address.clone(), &shares)?; // Prepare response let mut res = Response::new().add_attribute("method", "ack_add_liquidity"); - // Token 1 - let token_info = liquidity_info.pair_info.token_1.clone(); - let liquidity = liquidity_info.token_1_liquidity; - let escrow_contract = TOKEN_TO_ESCROW.load(deps.storage, token_info.token.clone())?; - - let send_msg = token_info.create_escrow_msg(liquidity, escrow_contract)?; - res = res.add_message(send_msg); + // Send tokens back to escrow + for token_info in liquidity_info.pair_info.get_vec_token_info() { + // Vouchers are not escrowed + if token_info.token_type.is_voucher() { + continue; + } - // Token 2 - let token_info = liquidity_info.pair_info.token_2; - let liquidity = liquidity_info.token_2_liquidity; - let escrow_contract = TOKEN_TO_ESCROW.load(deps.storage, token_info.token.clone())?; - let send_msg = token_info.create_escrow_msg(liquidity, escrow_contract)?; - res = res.add_message(send_msg); + let liquidity = token_info.amount; + let escrow_contract = + TOKEN_TO_ESCROW.load(deps.storage, token_info.token.clone())?; + let send_msg = token_info + .token_type + .create_escrow_msg(liquidity, escrow_contract)?; + res = res.add_message(send_msg); + } // Mint cw20 tokens for sender // // Get cw20 contract address @@ -382,18 +517,18 @@ fn ack_add_liquidity( } // Prepare messages to refund tokens back to user let mut msgs: Vec = Vec::new(); - let msg = liquidity_info.pair_info.token_1.create_transfer_msg( - liquidity_info.token_1_liquidity, - sender.to_string(), - None, - )?; - msgs.push(msg); - let msg = liquidity_info.pair_info.token_2.create_transfer_msg( - liquidity_info.token_2_liquidity, - sender.to_string(), - None, - )?; - msgs.push(msg); + for token_info in liquidity_info.pair_info.get_vec_token_info() { + if token_info.token_type.is_voucher() { + continue; + } + let msg = token_info.token_type.create_transfer_msg( + token_info.amount, + sender.to_string(), + None, + None, + )?; + msgs.push(msg); + } Ok(Response::new() .add_attribute("method", "liquidity_tx_err_refund") @@ -424,8 +559,8 @@ fn ack_remove_liquidity( // Remove liquidity shares let shares = VLP_TO_LP_SHARES .may_load(deps.storage, data.vlp_address.clone())? - .unwrap_or(Uint128::zero()); - let shares = shares.checked_sub(data.burn_lp_tokens)?; + .unwrap_or(Int256::zero()); + let shares = shares.checked_sub(data.burn_lp_tokens.into())?; VLP_TO_LP_SHARES.save(deps.storage, data.vlp_address.clone(), &shares)?; // Prepare response @@ -450,7 +585,6 @@ fn ack_remove_liquidity( .add_attribute("tx_id", tx_id)) } - // Todo:: Return LP Tokens back to sender AcknowledgementMsg::Error(err) => { // Its a native call so you can return error to reject complete execution call if is_native { @@ -492,20 +626,16 @@ fn ack_swap_request( // Check whether res is an error or not match res { AcknowledgementMsg::Ok(data) => { - // TODO:: Add msg to send asset_in to escrow let asset_in = swap_info.asset_in.clone(); - // Get corresponding escrow - let escrow_address = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; - - let send_msg = asset_in.create_escrow_msg(swap_info.amount_in, escrow_address)?; let mut response = Response::new() .add_event(swap_event(&tx_id, &swap_info)) .add_attribute("method", "process_successfull_swap") - .add_message(send_msg) .add_attribute("tx_id", tx_id) .add_attribute("amount_out", data.amount_out) - .add_attribute("swap_response", format!("{data:?}")); + .add_attribute("swap_response", format!("{data:?}")) + .add_attribute("partner_fee_amount", swap_info.partner_fee_amount) + .add_attribute("partner_fee_recipient", &swap_info.partner_fee_recipient); if !swap_info.partner_fee_amount.is_zero() { let mut state = STATE.load(deps.storage)?; @@ -517,26 +647,88 @@ fn ack_swap_request( // Save new total partner fees collected to state STATE.save(deps.storage, &state)?; + } + if !asset_in.token_type.is_voucher() { + let escrow_address = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; + let send_msg = asset_in.create_escrow_msg(swap_info.amount_in, escrow_address)?; + response = response.add_message(send_msg); + + // if partner fee is not zero, send it to the partner fee recipient + if !swap_info.partner_fee_amount.is_zero() { + let partner_send_msg = asset_in.create_transfer_msg( + swap_info.partner_fee_amount, + swap_info.partner_fee_recipient.to_string(), + None, + None, + )?; + response = response.add_message(partner_send_msg) + } + } - // Send the partner fee amount to recipient of the fee, if no recipient was provided, send the - // funds back to the user - let partner_fee_recipient = swap_info - .partner_fee_recipient - .unwrap_or(sender) - .to_string(); - let partner_send_msg = asset_in.create_transfer_msg( - swap_info.partner_fee_amount, - partner_fee_recipient.clone(), + Ok(response) + } + + AcknowledgementMsg::Error(err) => { + // Its a native call so you can return error to reject complete execution call + if is_native { + return Err(ContractError::new(&err)); + } + let mut response = Response::new() + .add_attribute("method", "process_failed_swap") + .add_attribute("refund_to", &sender) + .add_attribute("tx_id", tx_id) + .add_attribute("refund_amount", swap_info.amount_in) + .add_attribute("error", err); + // Prepare messages to refund tokens back to user + // Send back both amount in and fee amount + // NOTE - Only return the amount in if the token is not a voucher + if !swap_info.asset_in.token_type.is_voucher() { + let msg = swap_info.asset_in.create_transfer_msg( + swap_info + .amount_in + .checked_add(swap_info.partner_fee_amount)?, + sender.to_string(), + None, None, )?; - response = response - .add_message(partner_send_msg) - .add_attribute("partner_fee_amount", swap_info.partner_fee_amount) - .add_attribute("partner_fee_recipient", partner_fee_recipient) + response = response.add_message(msg); } Ok(response) } + } +} + +fn ack_deposit_token_request( + deps: DepsMut, + res: AcknowledgementMsg, + sender: String, + tx_id: String, + is_native: bool, +) -> Result { + let sender = deps.api.addr_validate(&sender)?; + // Validate that the pending swap exists for the sender + let deposit_info = PENDING_TOKEN_DEPOSIT.load(deps.storage, (sender.clone(), tx_id.clone()))?; + // Remove this from pending swaps + PENDING_TOKEN_DEPOSIT.remove(deps.storage, (sender.clone(), tx_id.clone())); + // Check whether res is an error or not + match res { + AcknowledgementMsg::Ok(data) => { + let asset_in = deposit_info.asset_in.clone(); + + // Get corresponding escrow + let escrow_address = TOKEN_TO_ESCROW.load(deps.storage, asset_in.token.clone())?; + + let send_msg = asset_in.create_escrow_msg(data.amount, escrow_address)?; + let response = Response::new() + .add_event(deposit_token_event(&tx_id, &deposit_info)) + .add_attribute("method", "process_successfull_deposit_token") + .add_message(send_msg) + .add_attribute("tx_id", tx_id) + .add_attribute("deposit_token_response", format!("{data:?}")); + + Ok(response) + } AcknowledgementMsg::Error(err) => { // Its a native call so you can return error to reject complete execution call @@ -545,19 +737,18 @@ fn ack_swap_request( } // Prepare messages to refund tokens back to user // Send back both amount in and fee amount - let msg = swap_info.asset_in.create_transfer_msg( - swap_info - .amount_in - .checked_add(swap_info.partner_fee_amount)?, + let msg = deposit_info.asset_in.create_transfer_msg( + deposit_info.amount_in, sender.to_string(), None, + None, )?; Ok(Response::new() - .add_attribute("method", "process_failed_swap") + .add_attribute("method", "process_failed_deposit_token") .add_attribute("refund_to", sender) .add_attribute("tx_id", tx_id) - .add_attribute("refund_amount", swap_info.amount_in) + .add_attribute("refund_amount", deposit_info.amount_in) .add_attribute("error", err) .add_message(msg)) } @@ -592,3 +783,32 @@ fn ack_withdraw_request( } } } + +fn ack_transfer_request( + _deps: DepsMut, + res: AcknowledgementMsg, + _sender: String, + token_id: Token, + _tx_id: String, + is_native: bool, +) -> Result { + match res { + AcknowledgementMsg::Ok(_data) => { + // Use it for logging, Router will send packets instead of ack to release tokens from escrow + // Here you will get a response of escrows that router is going to release so it can be used in frontend + + Ok(Response::new() + .add_attribute("method", "transfer") + .add_attribute("token", token_id.to_string())) + } + AcknowledgementMsg::Error(err) => { + // Its a native call so you can return error to reject complete execution call + if is_native { + return Err(ContractError::new(&err)); + } + Ok(Response::new() + .add_attribute("method", "transfer_error") + .add_attribute("error", err.clone())) + } + } +} diff --git a/contracts/liquidity/factory/src/ibc/receive.rs b/contracts/liquidity/factory/src/ibc/receive.rs index 5095ac5c..7bd083c9 100644 --- a/contracts/liquidity/factory/src/ibc/receive.rs +++ b/contracts/liquidity/factory/src/ibc/receive.rs @@ -2,10 +2,10 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ ensure, from_json, to_json_binary, CosmosMsg, DepsMut, Env, IbcPacketReceiveMsg, - IbcReceiveResponse, Response, StdError, SubMsg, Uint128, WasmMsg, + IbcReceiveResponse, MessageInfo, Response, StdError, SubMsg, Uint128, WasmMsg, }; use euclid::{ - chain::ChainUid, + chain::{ChainUid, CrossChainUserWithLimit}, error::ContractError, events::{tx_event, TxType}, msgs::{ @@ -44,7 +44,6 @@ pub fn ibc_packet_receive( let tx_id = msg .map(|m| m.get_tx_id()) .unwrap_or("tx_id_not_found".to_string()); - Ok(IbcReceiveResponse::new() .add_attribute("method", "ibc_packet_receive") .add_attribute("tx_id", tx_id) @@ -55,10 +54,15 @@ pub fn ibc_packet_receive( pub fn ibc_receive_internal_call( deps: DepsMut, env: Env, + info: MessageInfo, msg: IbcPacketReceiveMsg, ) -> Result { - let router = msg.packet.src.port_id.replace("wasm.", ""); + ensure!( + info.sender == env.contract.address, + ContractError::Unauthorized {} + ); + let router = msg.packet.src.port_id.replace("wasm.", ""); let state = STATE.load(deps.storage)?; ensure!( state.router_contract == router, @@ -88,10 +92,10 @@ pub fn reusable_internal_call( HubIbcExecuteMsg::ReleaseEscrow { amount, token, - to_address, tx_id, + recipient, .. - } => execute_release_escrow(deps, env, amount, token, to_address, tx_id), + } => execute_release_escrow(deps, env, amount, recipient, token, tx_id), HubIbcExecuteMsg::UpdateFactoryChannel { chain_uid, tx_id } => { execute_update_factory_channel(deps, env, chain_uid, tx_id) } @@ -161,17 +165,21 @@ fn execute_update_factory_channel( .add_attribute("router", state.router_contract) .set_data(ack)) } + fn execute_release_escrow( deps: DepsMut, env: Env, amount: Uint128, + recipient: CrossChainUserWithLimit, token: Token, - to_address: String, tx_id: String, ) -> Result { let withdraw_msg = EscrowExecuteMsg::Withdraw { - recipient: deps.api.addr_validate(&to_address)?, + recipient: deps.api.addr_validate(&recipient.user.address)?, amount, + preferred_denom: recipient.preferred_denom, + forwarding_message: recipient.forwarding_message, + refund_address: recipient.refund_address, }; let ack_msg = ReleaseEscrowResponse { @@ -179,7 +187,7 @@ fn execute_release_escrow( chain_id: env.block.chain_id, amount, token: token.clone(), - to_address: to_address.clone(), + to_address: recipient.user.address.clone(), }; let ack = to_json_binary(&AcknowledgementMsg::Ok(ack_msg))?; @@ -195,8 +203,10 @@ fn execute_release_escrow( msg: to_json_binary(&withdraw_msg)?, funds: vec![], })) - .add_attribute("method", "release escrow") + .add_attribute("method", "release escrow_execute") + .add_attribute("token", token.to_string()) + .add_attribute("amount", amount.to_string()) .add_attribute("tx_id", tx_id) - .add_attribute("to_address", to_address) + .add_attribute("to_address", recipient.user.address) .set_data(ack)) } diff --git a/contracts/liquidity/factory/src/interface.rs b/contracts/liquidity/factory/src/interface.rs new file mode 100644 index 00000000..7f626321 --- /dev/null +++ b/contracts/liquidity/factory/src/interface.rs @@ -0,0 +1,25 @@ +use crate::contract::{execute, instantiate, query}; +use cw_orch::{interface, prelude::*}; +use euclid::msgs::factory::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +pub const CONTRACT_ID: &str = "factory_contract"; + +#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)] +pub struct FactoryContract; + +// Implement the Uploadable trait so it can be uploaded to the mock. +impl Uploadable for FactoryContract { + fn wrapper() -> Box> { + Box::new( + ContractWrapper::new_with_empty(execute, instantiate, query) + .with_reply(crate::contract::reply) + .with_ibc( + crate::ibc::channel::ibc_channel_open, + crate::ibc::channel::ibc_channel_connect, + crate::ibc::channel::ibc_channel_close, + crate::ibc::receive::ibc_packet_receive, + crate::ibc::ack_and_timeout::ibc_packet_ack, + crate::ibc::ack_and_timeout::ibc_packet_timeout, + ), + ) + } +} diff --git a/contracts/liquidity/factory/src/lib.rs b/contracts/liquidity/factory/src/lib.rs index e436017e..115eea1f 100644 --- a/contracts/liquidity/factory/src/lib.rs +++ b/contracts/liquidity/factory/src/lib.rs @@ -8,7 +8,14 @@ pub mod migrate; pub mod query; pub mod reply; pub mod state; + +#[cfg(test)] pub mod tests; #[cfg(not(target_arch = "wasm32"))] pub mod mock; + +#[cfg(not(target_arch = "wasm32"))] +mod interface; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::interface::FactoryContract; diff --git a/contracts/liquidity/factory/src/migrate.rs b/contracts/liquidity/factory/src/migrate.rs index 6dea066b..a9ad897c 100644 --- a/contracts/liquidity/factory/src/migrate.rs +++ b/contracts/liquidity/factory/src/migrate.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{entry_point, DepsMut, Env, Response}; -use euclid::{error::ContractError, msgs::vlp::MigrateMsg}; +use euclid::{error::ContractError, msgs::factory::MigrateMsg}; /// This is the migrate entry point for the contract. /// Currently, it does not perform any migration logic and simply returns an empty response. diff --git a/contracts/liquidity/factory/src/query.rs b/contracts/liquidity/factory/src/query.rs index ca1d65bb..c8e62980 100644 --- a/contracts/liquidity/factory/src/query.rs +++ b/contracts/liquidity/factory/src/query.rs @@ -9,7 +9,7 @@ use euclid::{ PoolVlpResponse, StateResponse, }, token::{Pair, Token}, - utils::Pagination, + utils::pagination::Pagination, }; use crate::state::{ @@ -83,7 +83,7 @@ pub fn query_all_pools(deps: Deps) -> Result { let pools = PAIR_TO_VLP .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) .flat_map(|item| -> Result<_, ContractError> { - let item = item.unwrap(); + let item = item?; Ok(PoolVlpResponse { pair: Pair::new(item.0 .0, item.0 .1)?, vlp: item.1, diff --git a/contracts/liquidity/factory/src/reply.rs b/contracts/liquidity/factory/src/reply.rs index 13ec8ea8..f1c8ff2a 100644 --- a/contracts/liquidity/factory/src/reply.rs +++ b/contracts/liquidity/factory/src/reply.rs @@ -1,6 +1,6 @@ use crate::{ ibc, - state::{TOKEN_TO_ESCROW, VLP_TO_CW20}, + state::{PENDING_DEPOSIT_TOKEN, TOKEN_TO_ESCROW, VLP_TO_CW20}, }; use cosmwasm_std::{from_json, DepsMut, Env, Reply, Response, SubMsgResult}; use cw_utils::{parse_execute_response_data, parse_reply_instantiate_data}; @@ -26,10 +26,24 @@ pub fn on_escrow_instantiate_reply(deps: DepsMut, msg: Reply) -> Result = Item::new("hub_channel"); pub const PAIR_TO_VLP: Map<(Token, Token), String> = Map::new("pair_to_vlp"); // Map vlp to LP Allocations -pub const VLP_TO_LP_SHARES: Map = Map::new("vlp_to_lp_shares"); +pub const VLP_TO_LP_SHARES: Map = Map::new("vlp_to_lp_shares"); // New Factory states pub const TOKEN_TO_ESCROW: Map = Map::new("token_to_escrow"); @@ -50,15 +51,26 @@ pub const VLP_TO_CW20: Map = Map::new("vlp_to_cw20"); pub const PENDING_POOL_REQUESTS: Map<(Addr, String), PoolCreateRequest> = Map::new("request_to_pool"); -pub const PENDING_ESCROW_REQUESTS: Map<(Addr, String), EscrowCreateRequest> = - Map::new("request_to_pool"); +pub const PENDING_DENOM_REGISTER_DEREGISTER_REQUESTS: Map< + (Addr, String), + DenomRegisterDeregisterRequest, +> = Map::new("request_denom_register_deregister"); // Map for pending swaps for user pub const PENDING_SWAPS: Map<(Addr, String), SwapRequest> = Map::new("pending_swaps"); +// Map for pending token deposits for user +pub const PENDING_TOKEN_DEPOSIT: Map<(Addr, String), DepositTokenRequest> = + Map::new("pending_token_deposit"); + // Map for PENDING liquidity transactions pub const PENDING_ADD_LIQUIDITY: Map<(Addr, String), AddLiquidityRequest> = Map::new("pending_add_liquidity"); // Map for PENDING liquidity transactions pub const PENDING_REMOVE_LIQUIDITY: Map<(Addr, String), RemoveLiquidityRequest> = Map::new("pending_remove_liquidity"); + +pub const PENDING_DEPOSIT_TOKEN: Map = + Map::new("pending_deposit_token"); + +pub const FUNDS_INFO: Item = Item::new("funds_info"); diff --git a/contracts/liquidity/factory/src/tests.rs b/contracts/liquidity/factory/src/tests.rs index f7f55068..6fb90398 100644 --- a/contracts/liquidity/factory/src/tests.rs +++ b/contracts/liquidity/factory/src/tests.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] #[cfg(test)] mod tests { use crate::contract::{execute, instantiate}; diff --git a/packages/euclid/Cargo.toml b/packages/euclid/Cargo.toml index c1ac8db0..7ef7b528 100644 --- a/packages/euclid/Cargo.toml +++ b/packages/euclid/Cargo.toml @@ -1,6 +1,10 @@ [package] name = "euclid" -version = "0.1.0" +version = "0.2.1" +authors = [ + "gachouchani1999 ", + "Anshudhar Kumar Singh ", +] edition = "2021" @@ -13,7 +17,7 @@ library = [] crate-type = ["cdylib", "rlib"] [dependencies] -cosmwasm-std = { workspace = true } +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } cw-storage-plus = { workspace = true } itertools = { workspace = true } cosmwasm-schema = { workspace = true } @@ -23,6 +27,7 @@ cw20 = { workspace = true } serde = { workspace = true } schemars = { workspace = true } cw20-base = { workspace = true, features = ["library"] } +cw-orch = "=0.24.1" syn = "1.0" quote = "1.0" diff --git a/packages/euclid/src/chain.rs b/packages/euclid/src/chain.rs index e8e84e4d..68167ac4 100644 --- a/packages/euclid/src/chain.rs +++ b/packages/euclid/src/chain.rs @@ -4,7 +4,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, StdError, StdResult, Uint128}; use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; -use crate::error::ContractError; +use crate::{error::ContractError, msgs::hook::EuclidReceive, token::TokenType}; #[cw_serde] #[derive(PartialOrd)] @@ -91,12 +91,49 @@ impl CrossChainUser { address = self.address.as_str() ) } + + pub fn validate(&self) -> Result<&Self, ContractError> { + ensure!( + !self.address.is_empty(), + ContractError::Generic { + err: "Address cannot be empty".to_string() + } + ); + self.chain_uid.validate()?; + Ok(self) + } + + pub fn with_limit( + self, + limit: Option, + preferred_denom: Option, + refund_address: Option, + forwarding_message: Option, + ) -> CrossChainUserWithLimit { + CrossChainUserWithLimit { + user: self, + limit, + preferred_denom, + refund_address, + forwarding_message, + } + } +} + +#[cw_serde] +pub enum Limit { + LessThanOrEqual(Uint128), + Equal(Uint128), + GreaterThanOrEqual(Uint128), } #[cw_serde] pub struct CrossChainUserWithLimit { pub user: CrossChainUser, - pub limit: Option, + pub limit: Option, + pub preferred_denom: Option, + pub refund_address: Option, + pub forwarding_message: Option, } #[cw_serde] diff --git a/packages/euclid/src/deposit.rs b/packages/euclid/src/deposit.rs new file mode 100644 index 00000000..81dfdb64 --- /dev/null +++ b/packages/euclid/src/deposit.rs @@ -0,0 +1,28 @@ +use crate::{ + chain::CrossChainUser, + token::{Token, TokenWithDenom}, +}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{IbcTimeout, Uint128}; + +#[cw_serde] +pub struct DepositTokenRequest { + pub sender: String, + pub tx_id: String, + // The asset being swapped + pub asset_in: TokenWithDenom, + // The amount of asset being swapped + pub amount_in: Uint128, + // The timeout specified for the swap + pub timeout: IbcTimeout, + pub recipient: CrossChainUser, +} + +// Struct to handle Acknowledgement Response for a Deposit Token Request +#[cw_serde] +pub struct DepositTokenResponse { + pub amount: Uint128, + pub token: Token, + pub sender: CrossChainUser, + pub recipient: CrossChainUser, +} diff --git a/packages/euclid/src/error.rs b/packages/euclid/src/error.rs index 22478c10..2914d66c 100644 --- a/packages/euclid/src/error.rs +++ b/packages/euclid/src/error.rs @@ -1,8 +1,8 @@ use std::num::ParseIntError; use cosmwasm_std::{ - Addr, CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, - OverflowError, StdError, Uint128, + Addr, CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, + Decimal256, DivideByZeroError, OverflowError, StdError, Uint128, }; use cw20_base::ContractError as Cw20ContractError; use thiserror::Error; @@ -23,6 +23,9 @@ pub enum ContractError { #[error("{0}")] CheckedMultiplyRatioError(#[from] CheckedMultiplyRatioError), + #[error("{0}")] + CheckedFromRatioError(#[from] CheckedFromRatioError), + #[error("{0}")] DivideByZero(#[from] DivideByZeroError), @@ -32,6 +35,9 @@ pub enum ContractError { #[error("Error - {err}")] Generic { err: String }, + #[error("Unreachable Code")] + UnreachableCode {}, + #[error("Unauthorized")] Unauthorized {}, @@ -65,6 +71,24 @@ pub enum ContractError { #[error("UnsupportedDenomination")] UnsupportedDenomination {}, + #[error("Limit exceeded: {limit} < {amount}")] + LimitExceeded { limit: Uint128, amount: Uint128 }, + + #[error("Amount mismatch: expected {expected}, received {received}")] + AmountMismatch { + expected: Uint128, + received: Uint128, + }, + + #[error("Insufficient amount: min_amount {min_amount}, amount {amount}")] + InsufficientAmount { + min_amount: Uint128, + amount: Uint128, + }, + + #[error("CannotEscrowVoucher")] + CannotEscrowVoucher {}, + #[error("UnsupportedMessage")] UnsupportedMessage {}, @@ -73,7 +97,6 @@ pub enum ContractError { #[error("Not Implemented")] NotImplemented {}, - #[error("DenomDoesNotExist")] DenomDoesNotExist {}, @@ -95,8 +118,8 @@ pub enum ContractError { #[error("Pool already created for this chain")] PoolAlreadyExists {}, - #[error("Pool doesn't for this chain")] - PoolDoesNotExists {}, + #[error("Pool doesn't exist for this chain")] + PoolDoesNotExist {}, #[error("only unordered channels are supported")] OrderedChannel {}, @@ -107,6 +130,9 @@ pub enum ContractError { #[error("Invalid Token ID")] InvalidTokenID {}, + #[error("Virtual Balance Address Cannot Be Empty")] + EmptyVirtualBalanceAddress {}, + #[error("ChannelNotFound")] ChannelNotFound {}, @@ -170,8 +196,11 @@ pub enum ContractError { #[error("Liquity already exist in state for the sender")] LiquidityTxAlreadyExist {}, - #[error("Slippage has been exceeded when providing liquidity.")] - LiquiditySlippageExceeded {}, + #[error("Slippage has been exceeded when providing liquidity. Expected: {expected}, Received: {received}")] + LiquiditySlippageExceeded { + expected: Decimal256, + received: Decimal256, + }, #[error("Pool Instantiate Failed {err}")] PoolInstantiateFailed { err: String }, @@ -219,6 +248,17 @@ pub enum ContractError { #[error("Invalid expiration")] InvalidExpiration {}, // END CW20 ERRORS + #[error("Min received {received} is less than expected {expected}")] + MinReceived { + expected: Uint128, + received: Uint128, + }, + + #[error("Invalid Address: {address} {msg}")] + InvalidAddress { address: String, msg: String }, + + #[error("Unsupported Euclid Receive Message")] + UnsupportedEuclidReceiveMessage {}, } impl ContractError { diff --git a/packages/euclid/src/events.rs b/packages/euclid/src/events.rs index c6026c21..03a357a5 100644 --- a/packages/euclid/src/events.rs +++ b/packages/euclid/src/events.rs @@ -3,16 +3,34 @@ use core::fmt; use cosmwasm_schema::cw_serde; use cosmwasm_std::Event; -use crate::{pool::Pool, swap::SwapRequest}; +use crate::{ + deposit::DepositTokenRequest, + swap::SwapRequest, + token::{Token, TokenType, TokenWithAmount}, +}; -pub fn liquidity_event(pool: &Pool, tx_id: &str) -> Event { - simple_event() +pub fn liquidity_event( + pool: &[TokenWithAmount], + liquidity_change: &[TokenWithAmount], + tx_id: &str, +) -> Event { + let mut event = simple_event() .add_attribute("action", "liquidity_change") - .add_attribute("token_1_id", pool.pair.token_1.to_string()) - .add_attribute("token_1_liquidity", pool.reserve_1) - .add_attribute("token_2_id", pool.pair.token_2.to_string()) - .add_attribute("token_2_liquidity", pool.reserve_2) - .add_attribute("tx_id", tx_id) + .add_attribute("tx_id", tx_id); + + for token in pool { + event = event.add_attribute("token_id", token.token.to_string()); + event = event.add_attribute(format!("token_liquidity_{}", token.token), token.amount); + } + + for token in liquidity_change { + event = event.add_attribute( + format!("token_liquidity_change_{}", token.token), + token.amount, + ); + } + + event } pub fn swap_event(tx_id: &str, swap: &SwapRequest) -> Event { @@ -28,6 +46,16 @@ pub fn swap_event(tx_id: &str, swap: &SwapRequest) -> Event { .add_attribute("timeout", format!("{timeout:?}", timeout = swap.timeout)) } +pub fn deposit_token_event(tx_id: &str, deposit: &DepositTokenRequest) -> Event { + simple_event() + .add_attribute("action", "deposit_token") + .add_attribute("tx_id", tx_id) + .add_attribute("asset_in", deposit.asset_in.token.to_string()) + .add_attribute("asset_in_denom", deposit.asset_in.token_type.get_key()) + .add_attribute("amount_in", deposit.amount_in) + .add_attribute("timeout", format!("{timeout:?}", timeout = deposit.timeout)) +} + pub fn register_factory_event( tx_id: &str, factory_address: &str, @@ -45,11 +73,14 @@ pub fn register_factory_event( #[cw_serde] pub enum TxType { Swap, + DepositToken, AddLiquidity, RemoveLiquidity, PoolCreation, - EscrowCreation, + RegisterDenom, + DeregisterDenom, EscrowRelease, + TransferVirtualBalance, EscrowWithdraw, RegisterFactory, UpdateFactoryChannel, @@ -60,12 +91,15 @@ pub enum TxType { impl fmt::Display for TxType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { + TxType::DepositToken => "deposit_token", TxType::Swap => "swap", TxType::AddLiquidity => "add_liquidity", TxType::RemoveLiquidity => "remove_liquidity", TxType::PoolCreation => "pool_creation", - TxType::EscrowCreation => "escrow_creation", + TxType::RegisterDenom => "register_denom", + TxType::DeregisterDenom => "deregister_denom", TxType::EscrowRelease => "escrow_release", + TxType::TransferVirtualBalance => "transfer_virtual_balance", TxType::EscrowWithdraw => "escrow_withdraw", TxType::RegisterFactory => "register_factory", TxType::UpdateFactoryChannel => "update_factory_channel", @@ -77,10 +111,7 @@ impl fmt::Display for TxType { } pub fn tx_event(tx_id: &str, sender: &str, tx_type: TxType) -> Event { - let tx_type = match tx_type { - TxType::AddLiquidity => "add_liquidity".to_string(), - t => format!("{t:?}"), - }; + let tx_type = tx_type.to_string(); simple_event() .add_attribute("action", "transaction") .add_attribute("tx_id", tx_id) @@ -91,3 +122,17 @@ pub fn tx_event(tx_id: &str, sender: &str, tx_type: TxType) -> Event { pub fn simple_event() -> Event { Event::new("euclid").add_attribute("version", "1.0.0") } + +pub fn register_denom_event(token: &Token, chain_uid: &str, denom: &TokenType) -> Event { + Event::new("euclid-register-denom") + .add_attribute("token", token.to_string()) + .add_attribute(format!("{}_chain_uid", token), chain_uid) + .add_attribute(format!("{}_denom", token), denom.get_key()) +} + +pub fn deregister_denom_event(token: &Token, chain_uid: &str, denom: &TokenType) -> Event { + Event::new("euclid-deregister-denom") + .add_attribute("token", token.to_string()) + .add_attribute(format!("{}_chain_uid", token), chain_uid) + .add_attribute(format!("{}_denom", token), denom.get_key()) +} diff --git a/packages/euclid/src/fee.rs b/packages/euclid/src/fee.rs index 8c736a01..b51393fd 100644 --- a/packages/euclid/src/fee.rs +++ b/packages/euclid/src/fee.rs @@ -3,8 +3,12 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use std::collections::HashMap; +pub const BPS_100_PERCENT: u64 = 10000; +pub const BPS_10_PERCENT: u64 = 1000; +pub const BPS_1_PERCENT: u64 = 100; + // Set maximum fee as 10% -pub const MAX_FEE_BPS: u64 = 1000; +pub const MAX_FEE_BPS: u64 = BPS_10_PERCENT; // Fee Config for a VLP contract #[cw_serde] pub struct Fee { diff --git a/packages/euclid/src/lib.rs b/packages/euclid/src/lib.rs index 867707e1..61b20b00 100644 --- a/packages/euclid/src/lib.rs +++ b/packages/euclid/src/lib.rs @@ -1,5 +1,5 @@ pub mod chain; -pub mod cw20; +pub mod deposit; pub mod error; pub mod escrow; pub mod events; diff --git a/packages/euclid/src/liquidity.rs b/packages/euclid/src/liquidity.rs index d193b187..6c24cbfd 100644 --- a/packages/euclid/src/liquidity.rs +++ b/packages/euclid/src/liquidity.rs @@ -1,15 +1,16 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; -use crate::token::{Pair, PairWithDenom}; +use crate::{ + chain::CrossChainUser, + token::{Pair, PairWithAmount, PairWithDenomAndAmount}, +}; #[cw_serde] pub struct AddLiquidityRequest { pub sender: String, pub tx_id: String, - pub token_1_liquidity: Uint128, - pub token_2_liquidity: Uint128, - pub pair_info: PairWithDenom, + pub pair_info: PairWithDenomAndAmount, } // Struct to handle Acknowledgement Response for a Liquidity Request @@ -17,6 +18,8 @@ pub struct AddLiquidityRequest { pub struct AddLiquidityResponse { pub mint_lp_tokens: Uint128, pub vlp_address: String, + pub tx_id: String, + pub sender: CrossChainUser, } #[cw_serde] @@ -31,8 +34,7 @@ pub struct RemoveLiquidityRequest { // Struct to handle Acknowledgement Response for a Liquidity Request #[cw_serde] pub struct RemoveLiquidityResponse { - pub token_1_liquidity: Uint128, - pub token_2_liquidity: Uint128, + pub liquidity_removed: PairWithAmount, pub burn_lp_tokens: Uint128, pub vlp_address: String, } diff --git a/packages/euclid/src/msgs/cw20.rs b/packages/euclid/src/msgs/cw20.rs index 75ccd39d..9c16d367 100644 --- a/packages/euclid/src/msgs/cw20.rs +++ b/packages/euclid/src/msgs/cw20.rs @@ -37,7 +37,13 @@ impl From for Cw20InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { + UpdateState { + token_pair: Option, + factory_address: Option, + vlp: Option, + }, /// Transfer is a base message to move tokens to another account without triggering actions Transfer { recipient: String, amount: Uint128 }, /// Burn is a base message to destroy tokens forever @@ -166,13 +172,13 @@ impl From for Cw20ExecuteMsg { description, marketing, }, - ExecuteMsg::UploadLogo(logo) => Cw20ExecuteMsg::UploadLogo(logo), + _ => panic!("Unsupported message"), } } } #[cw_serde] -#[derive(QueryResponses)] +#[derive(cw_orch::QueryFns, QueryResponses)] pub enum QueryMsg { //NOTE: Balance is included in andr_query /// Returns the current balance of the given address, 0 if unset. @@ -250,3 +256,6 @@ impl From for Cw20QueryMsg { } } } + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/packages/euclid/src/msgs/escrow/cw20.rs b/packages/euclid/src/msgs/escrow/cw20.rs new file mode 100644 index 00000000..867a4d0c --- /dev/null +++ b/packages/euclid/src/msgs/escrow/cw20.rs @@ -0,0 +1,6 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub enum EscrowCw20HookMsg { + Deposit {}, +} diff --git a/packages/euclid/src/msgs/escrow/mod.rs b/packages/euclid/src/msgs/escrow/mod.rs new file mode 100644 index 00000000..ea184d7b --- /dev/null +++ b/packages/euclid/src/msgs/escrow/mod.rs @@ -0,0 +1,4 @@ +pub mod cw20; +pub mod msg; + +pub use self::msg::*; diff --git a/packages/euclid/src/msgs/escrow.rs b/packages/euclid/src/msgs/escrow/msg.rs similarity index 67% rename from packages/euclid/src/msgs/escrow.rs rename to packages/euclid/src/msgs/escrow/msg.rs index 6107ce4e..ecf49c01 100644 --- a/packages/euclid/src/msgs/escrow.rs +++ b/packages/euclid/src/msgs/escrow/msg.rs @@ -1,4 +1,7 @@ -use crate::token::{Pair, Token, TokenType}; +use crate::{ + msgs::hook::EuclidReceive, + token::{Pair, Token, TokenType}, +}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Uint128}; use cw20::Cw20ReceiveMsg; @@ -12,24 +15,36 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { // Updates allowed denoms - AddAllowedDenom { denom: TokenType }, + AddAllowedDenom { + denom: TokenType, + }, // Removes a denom from allowed denoms - DisallowDenom { denom: TokenType }, + DisallowDenom { + denom: TokenType, + }, DepositNative {}, - // ReleaseTokens { recipient: Addr, amount: Uint128 }, - // Recieve CW20 TOKENS structure Receive(Cw20ReceiveMsg), // Have a separate Msg for cw20 tokens? flow should be better if the message is unified - Withdraw { recipient: Addr, amount: Uint128 }, + Withdraw { + recipient: Addr, + amount: Uint128, + preferred_denom: Option, + forwarding_message: Option, + refund_address: Option, + }, } #[cw_serde] -#[derive(QueryResponses)] +#[derive(cw_orch::QueryFns, QueryResponses)] pub enum QueryMsg { + #[returns(StateResponse)] + State {}, + // New escrow queries #[returns(TokenIdResponse)] TokenId {}, @@ -45,6 +60,13 @@ pub enum QueryMsg { #[cw_serde] pub struct MigrateMsg {} +#[cw_serde] +pub struct StateResponse { + pub token: Token, + pub factory_address: Addr, + pub total_amount: Uint128, +} + #[cw_serde] pub struct TokenIdResponse { pub token_id: String, diff --git a/packages/euclid/src/cw20.rs b/packages/euclid/src/msgs/factory/cw20.rs similarity index 65% rename from packages/euclid/src/cw20.rs rename to packages/euclid/src/msgs/factory/cw20.rs index e1a96a7e..6272e231 100644 --- a/packages/euclid/src/cw20.rs +++ b/packages/euclid/src/msgs/factory/cw20.rs @@ -1,17 +1,21 @@ -use crate::{fee::PartnerFee, token::Pair}; use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use crate::{ - chain::CrossChainUserWithLimit, + chain::{CrossChainUser, CrossChainUserWithLimit}, + fee::PartnerFee, + msgs::hook::EuclidReceive, swap::NextSwapPair, - token::{Token, TokenWithDenom}, + token::{Pair, Token, TokenWithDenom}, }; -// CW20 Hook Msg #[cw_serde] -pub enum Cw20HookMsg { - Deposit {}, +pub enum FactoryCw20HookMsg { + Deposit { + token: Token, + timeout: Option, + recipient: Option, + }, Swap { asset_in: TokenWithDenom, asset_out: Token, @@ -28,4 +32,5 @@ pub enum Cw20HookMsg { // First element in array has highest priority cross_chain_addresses: Vec, }, + EuclidReceive(EuclidReceive), } diff --git a/packages/euclid/src/msgs/factory/euclid_receive.rs b/packages/euclid/src/msgs/factory/euclid_receive.rs new file mode 100644 index 00000000..7e50ed7e --- /dev/null +++ b/packages/euclid/src/msgs/factory/euclid_receive.rs @@ -0,0 +1,23 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Uint128; + +use crate::{ + chain::{CrossChainUser, CrossChainUserWithLimit}, + fee::PartnerFee, + swap::NextSwapPair, + token::{Token, TokenWithDenom}, +}; + +#[cw_serde] +pub enum FactoryEuclidReceiveHook { + Swap { + sender: Option, + asset_in: TokenWithDenom, + asset_out: Token, + min_amount_out: Uint128, + swaps: Vec, + timeout: Option, + cross_chain_addresses: Vec, + partner_fee: Option, + }, +} diff --git a/packages/euclid/src/msgs/factory/mod.rs b/packages/euclid/src/msgs/factory/mod.rs new file mode 100644 index 00000000..5c110fd6 --- /dev/null +++ b/packages/euclid/src/msgs/factory/mod.rs @@ -0,0 +1,5 @@ +pub mod cw20; +pub mod euclid_receive; +pub mod msg; + +pub use msg::*; diff --git a/packages/euclid/src/msgs/factory.rs b/packages/euclid/src/msgs/factory/msg.rs similarity index 74% rename from packages/euclid/src/msgs/factory.rs rename to packages/euclid/src/msgs/factory/msg.rs index f8a0b30b..05751676 100644 --- a/packages/euclid/src/msgs/factory.rs +++ b/packages/euclid/src/msgs/factory/msg.rs @@ -1,10 +1,11 @@ use crate::{ - chain::{ChainUid, CrossChainUserWithLimit}, + chain::{ChainUid, CrossChainUser, CrossChainUserWithLimit}, fee::{DenomFees, PartnerFee}, liquidity::{AddLiquidityRequest, RemoveLiquidityRequest}, + msgs::hook::EuclidReceive, swap::{NextSwapPair, SwapRequest}, - token::{Pair, PairWithDenom, Token, TokenType, TokenWithDenom}, - utils::Pagination, + token::{Pair, PairWithDenomAndAmount, Token, TokenType, TokenWithDenom}, + utils::pagination::Pagination, }; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, IbcPacketAckMsg, IbcPacketReceiveMsg, Uint128}; @@ -21,44 +22,31 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { AddLiquidityRequest { - pair_info: PairWithDenom, - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - slippage_tolerance: u64, + pair_info: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, timeout: Option, }, - ExecuteSwapRequest { - asset_in: TokenWithDenom, - asset_out: Token, - amount_in: Uint128, - min_amount_out: Uint128, - timeout: Option, - swaps: Vec, - // First element in array has highest priority - cross_chain_addresses: Vec, - - partner_fee: Option, - }, + ExecuteSwapRequest(ExecuteSwapRequest), RequestRegisterDenom { token: TokenWithDenom, + timeout: Option, }, RequestDeregisterDenom { token: TokenWithDenom, + timeout: Option, }, RequestPoolCreation { - pair: PairWithDenom, + pair: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, timeout: Option, lp_token_name: String, lp_token_symbol: String, lp_token_decimal: u8, lp_token_marketing: Option, }, - RequestRegisterEscrow { - token: TokenWithDenom, - timeout: Option, - }, UpdateHubChannel { new_channel: String, }, @@ -68,10 +56,34 @@ pub enum ExecuteMsg { cross_chain_addresses: Vec, timeout: Option, }, - + TransferVirtualBalance { + token: Token, + amount: Uint128, + recipient_address: CrossChainUser, + timeout: Option, + }, + DepositToken { + asset_in: TokenWithDenom, + amount_in: Uint128, + timeout: Option, + recipient: Option, + }, + UpdateFactoryState { + // The Router Contract Address on the Virtual Settlement Layer + router_contract: Option, + // Contract admin + admin: Option, + // Escrow Code ID + escrow_code_id: Option, + // CW20 Code ID + cw20_code_id: Option, + is_native: Option, + }, // Recieve CW20 TOKENS structure Receive(Cw20ReceiveMsg), + EuclidReceive(EuclidReceive), + // IBC Callbacks IbcCallbackAckAndTimeout { ack: IbcPacketAckMsg, @@ -86,7 +98,21 @@ pub enum ExecuteMsg { } #[cw_serde] -#[derive(QueryResponses)] +pub struct ExecuteSwapRequest { + pub sender: Option, + pub asset_in: TokenWithDenom, + pub amount_in: Uint128, + pub asset_out: Token, + pub min_amount_out: Uint128, + pub timeout: Option, + pub swaps: Vec, + // First element in array has highest priority + pub cross_chain_addresses: Vec, + pub partner_fee: Option, +} + +#[cw_serde] +#[derive(cw_orch::QueryFns, QueryResponses)] pub enum QueryMsg { #[returns(GetVlpResponse)] GetVlp { pair: Pair }, diff --git a/packages/euclid/src/msgs/hook.rs b/packages/euclid/src/msgs/hook.rs new file mode 100644 index 00000000..235bb588 --- /dev/null +++ b/packages/euclid/src/msgs/hook.rs @@ -0,0 +1,25 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, Binary}; + +use crate::error::ContractError; + +#[cw_serde] +pub struct EuclidReceive { + pub data: Binary, + // Metadata to be logged into events for some off chain oracle/analytics + pub meta: Option, +} + +// This is just a helper to properly serialize the above message +#[cw_serde] +pub enum EuclidReceiverMsg { + EuclidReceive(EuclidReceive), +} + +impl EuclidReceive { + pub fn to_receiver_msg(&self) -> Result { + Ok(to_json_binary(&EuclidReceiverMsg::EuclidReceive( + self.clone(), + ))?) + } +} diff --git a/packages/euclid/src/msgs/mod.rs b/packages/euclid/src/msgs/mod.rs index 95698a9a..59f3f792 100644 --- a/packages/euclid/src/msgs/mod.rs +++ b/packages/euclid/src/msgs/mod.rs @@ -1,6 +1,7 @@ pub mod cw20; pub mod escrow; pub mod factory; +pub mod hook; pub mod router; pub mod virtual_balance; pub mod vlp; diff --git a/packages/euclid/src/msgs/router.rs b/packages/euclid/src/msgs/router.rs index 1cdb54ff..29f0874e 100644 --- a/packages/euclid/src/msgs/router.rs +++ b/packages/euclid/src/msgs/router.rs @@ -4,8 +4,8 @@ use cosmwasm_std::{Addr, Binary, IbcPacketAckMsg, IbcPacketReceiveMsg, Uint128}; use crate::{ chain::{Chain, ChainUid, CrossChainUser, CrossChainUserWithLimit}, swap::NextSwapPair, - token::{Pair, Token}, - utils::Pagination, + token::{Pair, Token, TokenType}, + utils::pagination::Pagination, }; #[cw_serde] pub struct InstantiateMsg { @@ -15,6 +15,7 @@ pub struct InstantiateMsg { } #[cw_serde] +#[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg { ReregisterChain { chain: ChainUid, @@ -27,10 +28,6 @@ pub enum ExecuteMsg { channel: String, }, UpdateLock {}, - // Update Pool Code ID - UpdateVLPCodeId { - new_vlp_code_id: u64, - }, RegisterFactory { chain_uid: ChainUid, chain_info: RegisterFactoryChainType, @@ -49,7 +46,6 @@ pub enum ExecuteMsg { timeout: Option, tx_id: String, }, - // IBC Callbacks IbcCallbackAckAndTimeout { ack: IbcPacketAckMsg, @@ -63,10 +59,18 @@ pub enum ExecuteMsg { msg: Binary, chain_uid: ChainUid, }, + UpdateRouterState { + // Contract admin + admin: Option, + // Pool Code ID + vlp_code_id: Option, + virtual_balance_address: Option, + locked: Option, + }, } #[cw_serde] -#[derive(QueryResponses)] +#[derive(cw_orch::QueryFns, QueryResponses)] pub enum QueryMsg { #[returns(StateResponse)] GetState {}, @@ -95,13 +99,20 @@ pub enum QueryMsg { token: Token, pagination: Pagination, }, + #[returns(AllEscrowsResponse)] + QueryAllEscrows { pagination: Pagination }, #[returns(AllTokensResponse)] QueryAllTokens { pagination: Pagination }, + + #[returns(TokenDenomsResponse)] + QueryTokenDenoms { token: Token }, } // We define a custom struct for each query response #[cw_serde] -pub struct MigrateMsg {} +pub struct MigrateMsg { + pub denoms: Vec<(Token, TokenDenom)>, +} #[cw_serde] pub struct QuerySimulateSwap { @@ -167,14 +178,31 @@ pub struct TokenEscrowChainResponse { } #[cw_serde] -pub struct TokenResponse { +pub struct EscrowResponse { pub token: Token, pub chain_uid: ChainUid, + pub balance: Uint128, +} + +#[cw_serde] +pub struct AllEscrowsResponse { + pub escrows: Vec, } #[cw_serde] pub struct AllTokensResponse { - pub tokens: Vec, + pub tokens: Vec, +} + +#[cw_serde] +pub struct TokenDenom { + pub chain_uid: ChainUid, + pub token_type: TokenType, +} + +#[cw_serde] +pub struct TokenDenomsResponse { + pub denoms: Vec, } #[cw_serde] diff --git a/packages/euclid/src/msgs/virtual_balance.rs b/packages/euclid/src/msgs/virtual_balance.rs index e4cf3c8d..f87f6ac7 100644 --- a/packages/euclid/src/msgs/virtual_balance.rs +++ b/packages/euclid/src/msgs/virtual_balance.rs @@ -20,6 +20,10 @@ pub enum ExecuteMsg { Mint(ExecuteMint), Transfer(ExecuteTransfer), Burn(ExecuteBurn), + UpdateState { + router: Option, + admin: Option, + }, } #[cw_serde] diff --git a/packages/euclid/src/msgs/vlp.rs b/packages/euclid/src/msgs/vlp.rs index b6bc82b8..345fa8ac 100644 --- a/packages/euclid/src/msgs/vlp.rs +++ b/packages/euclid/src/msgs/vlp.rs @@ -2,7 +2,7 @@ use crate::{ chain::{ChainUid, CrossChainUser}, fee::{Fee, TotalFees}, swap::NextSwapVlp, - token::{Pair, Token}, + token::{Pair, PairWithAmount, Token, TokenType}, }; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Uint128; @@ -26,7 +26,6 @@ pub enum ExecuteMsg { tx_id: String, }, - // Registers a new pool from a new chain to an already existing VLP UpdateFee { lp_fee_bps: Option, euclid_fee_bps: Option, @@ -45,24 +44,25 @@ pub enum ExecuteMsg { AddLiquidity { sender: CrossChainUser, tx_id: String, - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - slippage_tolerance: u64, + liquidity: PairWithAmount, + slippage_tolerance_bps: u64, }, RemoveLiquidity { sender: CrossChainUser, tx_id: String, lp_allocation: Uint128, }, - /* - - // Update the fee for the VLP - UpdateFee { - lp_fee: u64, - treasury_fee: u64, - staker_fee: u64, + UpdateState { + // Router Contract + router: Option, + // Virtual Coin Contract + virtual_balance: Option, + // Fee per swap for each transaction + fee: Option, + // The last timestamp where the balances for each token have been updated + last_updated: Option, + admin: Option, }, - */ } #[cw_serde] @@ -165,8 +165,8 @@ pub struct MigrateMsg {} #[cw_serde] pub struct VlpRemoveLiquidityResponse { - pub token_1_liquidity_released: Uint128, - pub token_2_liquidity_released: Uint128, + pub liquidity_released: PairWithAmount, + pub preferred_denom: Option, pub burn_lp_tokens: Uint128, pub tx_id: String, pub sender: CrossChainUser, diff --git a/packages/euclid/src/pool.rs b/packages/euclid/src/pool.rs index ecd17cdb..754e379e 100644 --- a/packages/euclid/src/pool.rs +++ b/packages/euclid/src/pool.rs @@ -2,55 +2,38 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Uint128; use crate::{ - error::ContractError, - token::{Pair, PairWithDenom, Token, TokenWithDenom}, + chain::CrossChainUser, + token::{PairWithDenomAndAmount, TokenWithDenom}, }; pub const MINIMUM_LIQUIDITY: u128 = 1000; +// Request to create pool saved in state to manage during acknowledgement #[cw_serde] -pub struct Pool { - pub pair: Pair, - // The total reserve of token_1 - pub reserve_1: Uint128, - // The total reserve of token_2 - pub reserve_2: Uint128, -} - -impl Pool { - pub fn new(pair: Pair, reserve_1: Uint128, reserve_2: Uint128) -> Pool { - Pool { - pair, - reserve_1, - reserve_2, - } - } - - pub fn get_reserve(&self, token: Token) -> Result { - if token == self.pair.token_1 { - Ok(self.reserve_1) - } else if token == self.pair.token_2 { - Ok(self.reserve_2) - } else { - Err(ContractError::AssetDoesNotExist {}) - } - } +pub struct PoolCreateRequest { + // Request sender + pub sender: String, + // Pool request id + pub tx_id: String, + // Pool Pair + pub pair_info: PairWithDenomAndAmount, + pub lp_token_instantiate_msg: cw20_base::msg::InstantiateMsg, } // Request to create pool saved in state to manage during acknowledgement #[cw_serde] -pub struct PoolCreateRequest { +pub struct PoolWithLiquidityCreateRequest { // Request sender pub sender: String, // Pool request id pub tx_id: String, // Pool Pair - pub pair_info: PairWithDenom, + pub pair_info: PairWithDenomAndAmount, pub lp_token_instantiate_msg: cw20_base::msg::InstantiateMsg, } #[cw_serde] -pub struct EscrowCreateRequest { +pub struct DenomRegisterDeregisterRequest { // Request sender pub sender: String, // Escrow request id @@ -63,7 +46,13 @@ pub struct EscrowCreateRequest { #[cw_serde] pub struct PoolCreationResponse { pub vlp_contract: String, + pub tx_id: String, + pub mint_lp_tokens: Uint128, + pub sender: CrossChainUser, } #[cw_serde] -pub struct EscrowCreationResponse {} +pub struct RegisterDenomResponse {} + +#[cw_serde] +pub struct DeRegisterDenomResponse {} diff --git a/packages/euclid/src/swap.rs b/packages/euclid/src/swap.rs index c78b3baa..346d07e7 100644 --- a/packages/euclid/src/swap.rs +++ b/packages/euclid/src/swap.rs @@ -14,10 +14,10 @@ pub struct SwapRequest { // The asset being swapped pub asset_in: TokenWithDenom, + // The amount of asset_in being swapped + pub amount_in: Uint128, // The asset being received pub asset_out: Token, - // The amount of asset being swapped - pub amount_in: Uint128, // The min amount of asset being received pub min_amount_out: Uint128, // All the swaps needed for assent_in <> asset_out @@ -28,7 +28,7 @@ pub struct SwapRequest { pub cross_chain_addresses: Vec, pub partner_fee_amount: Uint128, - pub partner_fee_recipient: Option, + pub partner_fee_recipient: Addr, } #[cw_serde] @@ -55,3 +55,9 @@ pub struct WithdrawResponse { pub token: Token, pub tx_id: String, } + +#[cw_serde] +pub struct TransferResponse { + pub token: Token, + pub tx_id: String, +} diff --git a/packages/euclid/src/token.rs b/packages/euclid/src/token.rs index cbed0119..391269de 100644 --- a/packages/euclid/src/token.rs +++ b/packages/euclid/src/token.rs @@ -3,15 +3,14 @@ use std::ops::Deref; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - coin, ensure, forward_ref_partial_eq, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Deps, - StdError, StdResult, Uint128, WasmMsg, + coin, ensure, forward_ref_partial_eq, to_json_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, + Deps, StdError, StdResult, Uint128, WasmMsg, }; use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; use crate::chain::CrossChainUser; -use crate::cw20::Cw20HookMsg; +use crate::error::ContractError; use crate::msgs::virtual_balance::ExecuteTransfer; -use crate::{error::ContractError, pool::Pool}; // Token asset that represents an identifier for a token #[cw_serde] @@ -75,6 +74,20 @@ impl Token { }; Ok(transfer_msg) } + + pub fn with_amount(&self, amount: Uint128) -> TokenWithAmount { + TokenWithAmount { + token: self.clone(), + amount, + } + } + + pub fn with_type(&self, token_type: TokenType) -> TokenWithDenom { + TokenWithDenom { + token: self.clone(), + token_type, + } + } } impl<'a> PrimaryKey<'a> for Token { @@ -121,7 +134,7 @@ forward_ref_partial_eq!(Pair, Pair); impl Pair { pub fn new(token_1: Token, token_2: Token) -> Result { - let pair = if token_1.le(&token_2) { + let pair = if token_1.le(&token_2.to_string()) { Self { token_1, token_2 } } else { Self { @@ -142,7 +155,7 @@ impl Pair { self.token_2.validate()?; ensure!( - self.token_1.le(&self.token_2), + self.token_1.le(&self.token_2.to_string()), ContractError::new("Token order is wrong") ); Ok(()) @@ -156,25 +169,28 @@ impl Pair { } pub fn get_tupple(&self) -> (Token, Token) { - if self.token_1.le(&self.token_2) { + if self.token_1.le(&self.token_2.to_string()) { (self.token_1.clone(), self.token_2.clone()) } else { (self.token_2.clone(), self.token_1.clone()) } } - pub fn get_pool(&self, reserve_1: Uint128, reserve_2: Uint128) -> Pool { - Pool { - pair: self.clone(), - reserve_1, - reserve_2, - } - } - pub fn get_vec_token(&self) -> Vec { let tokens: Vec = vec![self.token_1.clone(), self.token_2.clone()]; tokens } + + pub fn get_pair_with_amount( + &self, + reserve_1: Uint128, + reserve_2: Uint128, + ) -> Result { + PairWithAmount::new( + self.token_1.with_amount(reserve_1), + self.token_2.with_amount(reserve_2), + ) + } } impl<'a> PrimaryKey<'a> for Pair { @@ -233,26 +249,75 @@ impl KeyDeserialize for Pair { pub enum TokenType { Native { denom: String }, Smart { contract_address: String }, + Voucher {}, } // Helper to Check if Token is Native or Smart impl TokenType { pub fn is_native(&self) -> bool { + matches!(self, TokenType::Native { .. }) + } + + pub fn is_smart(&self) -> bool { + matches!(self, TokenType::Smart { .. }) + } + + pub fn get_smart_contract_address(&self) -> Result { match self { - TokenType::Native { .. } => true, - TokenType::Smart { .. } => false, + TokenType::Smart { contract_address } => Ok(contract_address.clone()), + _ => Err(ContractError::new("Token is not smart")), } } - pub fn is_smart(&self) -> bool { - !self.is_native() + pub fn is_voucher(&self) -> bool { + matches!(self, TokenType::Voucher { .. }) } - // Helper to get the denom of a native or CW20 token - pub fn get_denom(&self) -> String { + /// Validates smart contract addresses, checks against empty denom and zero supply + pub fn validate(&self, deps: &Deps) -> Result<(), ContractError> { + if let Self::Native { denom } = &self { + let potential_supply = deps.querier.query_supply(denom.clone())?; + let non_zero_supply = !potential_supply.amount.is_zero(); + ensure!( + non_zero_supply, + ContractError::ZeroAssetSupply { + asset: denom.clone() + } + ); + } else if let Self::Smart { contract_address } = &self { + let contract = deps + .querier + .query_wasm_contract_info(contract_address.clone()); + ensure!( + contract.is_ok(), + ContractError::InvalidAsset { + asset: contract_address.clone() + } + ); + } + + // Vouchers will be validated in VSL + Ok(()) + } + + pub fn get_balance(&self, deps: Deps, address: String) -> Result { match self.clone() { - TokenType::Native { denom } => denom.to_string(), - TokenType::Smart { contract_address } => contract_address.to_string(), + TokenType::Native { denom } => { + let balance = deps.querier.query_balance(address, denom)?; + Ok(balance.amount) + } + TokenType::Smart { contract_address } => { + let balance_msg = cw20::Cw20QueryMsg::Balance { + address: address.clone(), + }; + let balance: cw20::BalanceResponse = deps + .querier + .query_wasm_smart(contract_address, &balance_msg)?; + Ok(balance.balance) + } + TokenType::Voucher { .. } => Err(ContractError::new( + "Cannot get balance of voucher using this function", + )), } } @@ -260,6 +325,7 @@ impl TokenType { match self.clone() { TokenType::Native { denom } => format!("native:{denom}"), TokenType::Smart { contract_address } => format!("smart:{contract_address}"), + TokenType::Voucher { .. } => "voucher".to_string(), } } @@ -269,29 +335,68 @@ impl TokenType { amount: Uint128, recipient: String, allowance: Option, + forwarding_message: Option, ) -> Result { let msg = match self.clone() { - TokenType::Native { denom } => CosmosMsg::Bank(BankMsg::Send { - to_address: recipient, - amount: vec![Coin { - denom: denom.to_string(), - amount, - }], - }), - TokenType::Smart { contract_address } => CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_address.to_string(), - msg: match allowance { - Some(owner) => to_json_binary(&cw20_base::msg::ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - })?, - None => { - to_json_binary(&cw20_base::msg::ExecuteMsg::Transfer { recipient, amount })? - } - }, - funds: vec![], - }), + TokenType::Native { denom } => { + if let Some(forwarding_message) = forwarding_message { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: recipient.to_string(), + msg: forwarding_message.clone(), + funds: vec![coin(amount.u128(), denom.clone())], + }) + } else { + CosmosMsg::Bank(BankMsg::Send { + to_address: recipient, + amount: vec![Coin { + denom: denom.to_string(), + amount, + }], + }) + } + } + TokenType::Smart { contract_address } => { + if let Some(forwarding_message) = forwarding_message { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: contract_address.to_string(), + msg: match allowance { + Some(owner) => to_json_binary(&cw20_base::msg::ExecuteMsg::SendFrom { + owner, + amount, + contract: recipient.to_string(), + msg: forwarding_message.clone(), + })?, + None => to_json_binary(&cw20_base::msg::ExecuteMsg::Send { + contract: recipient.to_string(), + msg: forwarding_message.clone(), + amount, + })?, + }, + funds: vec![], + }) + } else { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: contract_address.to_string(), + msg: match allowance { + Some(owner) => { + to_json_binary(&cw20_base::msg::ExecuteMsg::TransferFrom { + owner, + recipient, + amount, + })? + } + None => to_json_binary(&cw20_base::msg::ExecuteMsg::Transfer { + recipient, + amount, + })?, + }, + funds: vec![], + }) + } + } + TokenType::Voucher { .. } => { + return Err(ContractError::new("Voucher can only be transferred in vsl")); + } }; Ok(msg) } @@ -312,65 +417,63 @@ impl TokenType { msg: to_json_binary(&cw20_base::msg::ExecuteMsg::Send { contract: escrow_contract.to_string(), amount, - msg: to_json_binary(&Cw20HookMsg::Deposit {})?, + msg: to_json_binary(&crate::msgs::escrow::cw20::EscrowCw20HookMsg::Deposit {})?, })?, funds: vec![], }), + TokenType::Voucher { .. } => { + return Err(ContractError::new("Voucher is already in escrow")); + } }; Ok(msg) } } #[cw_serde] -pub struct TokenWithDenom { +pub struct TokenWithAmount { pub token: Token, - pub token_type: TokenType, + pub amount: Uint128, } -impl TokenWithDenom { - /// Validates smart contract addresses, checks against empty denom and zero supply - pub fn validate(&self, deps: Deps) -> Result<(), ContractError> { - let denom = self.token_type.get_denom(); - ensure!( - !denom.is_empty(), - ContractError::InvalidAsset { asset: denom } - ); +#[cw_serde] +pub struct TokenWithDenomAndAmount { + pub token: Token, + pub amount: Uint128, + pub token_type: TokenType, +} - if self.token_type.is_native() { - let potential_supply = deps.querier.query_supply(denom.clone())?; - let non_zero_supply = !potential_supply.amount.is_zero(); - ensure!( - non_zero_supply, - ContractError::ZeroAssetSupply { asset: denom } - ); +impl TokenWithDenomAndAmount { + pub fn to_token_with_amount(&self) -> TokenWithAmount { + TokenWithAmount { + token: self.token.clone(), + amount: self.amount, } - // else { - // let token_info_query: TokenInfoResponse = - // deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - // contract_addr: denom, - // msg: to_json_binary(&Cw20QueryMsg::TokenInfo {})?, - // }))?; - // ensure!( - // !token_info_query.total_supply.is_zero(), - // ContractError::InvalidZeroAmount {} - // ); - // } - - Ok(()) } - pub fn get_denom(&self) -> String { - self.token_type.get_denom() + pub fn to_token_with_denom(&self) -> TokenWithDenom { + TokenWithDenom { + token: self.token.clone(), + token_type: self.token_type.clone(), + } } +} + +#[cw_serde] +pub struct TokenWithDenom { + pub token: Token, + pub token_type: TokenType, +} +impl TokenWithDenom { pub fn create_transfer_msg( &self, amount: Uint128, recipient: String, allowance: Option, + forwarding_message: Option, ) -> Result { self.token_type - .create_transfer_msg(amount, recipient, allowance) + .create_transfer_msg(amount, recipient, allowance, forwarding_message) } pub fn create_escrow_msg( @@ -380,6 +483,44 @@ impl TokenWithDenom { ) -> Result { self.token_type.create_escrow_msg(amount, escrow_contract) } + + pub fn with_amount(&self, amount: Uint128) -> TokenWithDenomAndAmount { + TokenWithDenomAndAmount { + token: self.token.clone(), + amount, + token_type: self.token_type.clone(), + } + } +} + +#[cw_serde] +pub struct PairWithAmount { + pub token_1: TokenWithAmount, + pub token_2: TokenWithAmount, +} + +impl PairWithAmount { + pub fn new(token_1: TokenWithAmount, token_2: TokenWithAmount) -> Result { + let pair_with_amount = if token_1.token.le(&token_2.token.to_string()) { + Self { token_1, token_2 } + } else { + Self { + token_1: token_2, + token_2: token_1, + } + }; + pair_with_amount.get_pair()?.validate()?; + Ok(pair_with_amount) + } + + pub fn get_pair(&self) -> Result { + Pair::new(self.token_1.token.clone(), self.token_2.token.clone()) + } + + pub fn get_vec_token(&self) -> Vec { + let tokens: Vec = vec![self.token_1.clone(), self.token_2.clone()]; + tokens + } } #[cw_serde] @@ -392,23 +533,63 @@ impl PairWithDenom { pub fn get_pair(&self) -> Result { Pair::new(self.token_1.token.clone(), self.token_2.token.clone()) } + + pub fn get_pair_with_amount( + &self, + token_1_amount: Uint128, + token_2_amount: Uint128, + ) -> Result { + PairWithAmount::new( + self.token_1.token.with_amount(token_1_amount), + self.token_2.token.with_amount(token_2_amount), + ) + } + + pub fn with_amount( + &self, + token_1_amount: Uint128, + token_2_amount: Uint128, + ) -> Result { + Ok(PairWithDenomAndAmount { + token_1: self.token_1.with_amount(token_1_amount), + token_2: self.token_2.with_amount(token_2_amount), + }) + } + pub fn get_vec_token_info(&self) -> Vec { - let tokens: Vec = vec![self.token_1.clone(), self.token_2.clone()]; + let tokens = vec![self.token_1.clone(), self.token_2.clone()]; tokens } +} - pub fn validate(&self) -> Result { - let pair = self.get_pair()?; - ensure!( - pair.token_1 == self.token_1.token, - ContractError::new("Pair should be sorted") - ); - ensure!( - pair.token_2 == self.token_2.token, - ContractError::new("Pair should be sorted") - ); +#[cw_serde] +pub struct PairWithDenomAndAmount { + pub token_1: TokenWithDenomAndAmount, + pub token_2: TokenWithDenomAndAmount, +} - Ok(true) +impl PairWithDenomAndAmount { + pub fn get_pair(&self) -> Result { + Pair::new(self.token_1.token.clone(), self.token_2.token.clone()) + } + + pub fn get_pair_with_denom(&self) -> Result { + Ok(PairWithDenom { + token_1: self.token_1.to_token_with_denom(), + token_2: self.token_2.to_token_with_denom(), + }) + } + + pub fn get_pair_with_amount(&self) -> Result { + PairWithAmount::new( + self.token_1.to_token_with_amount(), + self.token_2.to_token_with_amount(), + ) + } + + pub fn get_vec_token_info(&self) -> Vec { + let tokens: Vec = vec![self.token_1.clone(), self.token_2.clone()]; + tokens } } @@ -431,6 +612,12 @@ mod tests { expected_error: Option, } + struct TestPairWithDenom { + name: &'static str, + pair_with_denom: PairWithDenomAndAmount, // Ensure PairWithDenom is also defined + expected_error: Option, + } + #[test] fn test_tuple_key_serialize_deserialzie() { let mut owned_deps = mock_dependencies(); @@ -528,4 +715,100 @@ mod tests { } } } + + #[test] + fn test_pair_with_denom_validation() { + let test_cases = vec![ + TestPairWithDenom { + name: "Duplicate tokens with denom", + pair_with_denom: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token("ABC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom1".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token("ABC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom2".to_string(), + }, + }, + }, + expected_error: Some(ContractError::DuplicateTokens {}), + }, + TestPairWithDenom { + name: "Different tokens with different denoms", + pair_with_denom: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token("ABC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom1".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token("DEF".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom2".to_string(), + }, + }, + }, + expected_error: None, + }, + TestPairWithDenom { + name: "Same letters but with different case and different denoms", + pair_with_denom: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token("ABC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom1".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token("AbC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom2".to_string(), + }, + }, + }, + expected_error: None, + }, + TestPairWithDenom { + name: "One invalid token with denom", + pair_with_denom: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token("ABC".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom1".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token("".to_string()), + amount: Uint128::from(100u128), + token_type: TokenType::Native { + denom: "denom2".to_string(), + }, + }, + }, + expected_error: Some(ContractError::InvalidTokenID {}), + }, + ]; + + for test in test_cases { + let res = test.pair_with_denom.get_pair(); + if let Some(err) = test.expected_error { + assert_eq!(res.unwrap_err(), err, "{}", test.name); + continue; + } else { + assert!(res.is_ok()) + } + } + } } diff --git a/packages/euclid/src/utils/fund_manager.rs b/packages/euclid/src/utils/fund_manager.rs new file mode 100644 index 00000000..9c4c137c --- /dev/null +++ b/packages/euclid/src/utils/fund_manager.rs @@ -0,0 +1,162 @@ +use std::collections::HashMap; + +use cosmwasm_std::{ensure, Coin, Uint128}; + +use crate::error::ContractError; + +pub struct FundManager { + funds: HashMap, +} + +impl FundManager { + /// Create a new fund manager + pub fn new(funds: &[Coin]) -> Self { + let mut fund_manager = FundManager { + funds: HashMap::new(), + }; + for fund in funds { + fund_manager.add(fund); + } + fund_manager + } + + /// Get the amount of funds in the manager for a given denom + pub fn get(&self, denom: &str) -> Uint128 { + self.funds.get(denom).cloned().unwrap_or(Uint128::zero()) + } + + /// Add funds to the manager + pub fn add(&mut self, fund: &Coin) { + *self + .funds + .entry(fund.denom.to_string()) + .or_insert(Uint128::zero()) += fund.amount; + } + + // Use funds from the manager + pub fn use_fund(&mut self, amount: Uint128, denom: &str) -> Result<(), ContractError> { + ensure!( + !amount.is_zero(), + ContractError::new("Amount cannot be zero") + ); + ensure!( + self.get(denom).ge(&amount), + ContractError::InsufficientFunds {} + ); + *self + .funds + .get_mut(denom) + .ok_or(ContractError::new("Denom not found"))? -= amount; + Ok(()) + } + + /// Validate that there are no zero funds in the manager + pub fn validate_non_zero_funds(&self) -> Result<(), ContractError> { + ensure!( + self.funds.iter().all(|(_, amount)| !amount.is_zero()), + ContractError::new("Funds cannot be zero") + ); + Ok(()) + } + + /// Validate that there are no funds in the manager. To be used after all funds operations are done. + pub fn validate_funds_are_empty(&self) -> Result<(), ContractError> { + ensure!( + self.funds.iter().all(|(_, amount)| amount.is_zero()), + ContractError::new("Funds should be empty") + ); + Ok(()) + } + + /// Validate that there are n number of funds in the manager + pub fn validate_n_funds(&self, n: usize) -> Result<(), ContractError> { + ensure!( + self.funds.len() == n, + ContractError::new(&format!("Expected {} funds, got {}", n, self.funds.len())) + ); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{Coin, Uint128}; + + use crate::error::ContractError; + + use super::*; + + #[test] + fn test_new() { + let fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!(fund_manager.get("atom"), Uint128::new(100)); + } + + #[test] + fn test_duplicate_funds() { + let fund_manager = FundManager::new(&[Coin::new(100, "atom"), Coin::new(200, "atom")]); + assert_eq!(fund_manager.get("atom"), Uint128::new(300)); + } + + #[test] + fn test_use_fund() { + let mut fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!(fund_manager.use_fund(Uint128::new(50), "atom"), Ok(())); + assert_eq!(fund_manager.get("atom"), Uint128::new(50)); + } + + #[test] + fn test_use_fund_insufficient() { + let mut fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!( + fund_manager.use_fund(Uint128::new(150), "atom"), + Err(ContractError::InsufficientFunds {}) + ); + } + + #[test] + fn test_validate_non_zero_funds() { + let fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!(fund_manager.validate_non_zero_funds(), Ok(())); + } + + #[test] + fn test_validate_non_zero_funds_empty() { + let fund_manager = FundManager::new(&[Coin::new(0, "atom")]); + assert_eq!( + fund_manager.validate_non_zero_funds(), + Err(ContractError::new("Funds cannot be zero")) + ); + } + + #[test] + fn test_validate_funds_are_empty() { + let fund_manager = FundManager::new(&[]); + assert_eq!(fund_manager.validate_funds_are_empty(), Ok(())); + } + + #[test] + fn test_funds_are_not_empty() { + let fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!( + fund_manager.validate_funds_are_empty(), + Err(ContractError::new("Funds should be empty")) + ); + } + + #[test] + fn test_validate_funds_are_empty_after_use() { + let mut fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + fund_manager.use_fund(Uint128::new(100), "atom").unwrap(); + assert_eq!(fund_manager.validate_funds_are_empty(), Ok(())); + } + + #[test] + fn test_insufficient_funds() { + let mut fund_manager = FundManager::new(&[Coin::new(100, "atom")]); + assert_eq!( + fund_manager.use_fund(Uint128::new(150), "atom"), + Err(ContractError::InsufficientFunds {}) + ); + } +} diff --git a/packages/euclid/src/utils/mod.rs b/packages/euclid/src/utils/mod.rs new file mode 100644 index 00000000..d8d2e631 --- /dev/null +++ b/packages/euclid/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod fund_manager; +pub mod pagination; +pub mod tx; diff --git a/packages/euclid/src/utils/pagination.rs b/packages/euclid/src/utils/pagination.rs new file mode 100644 index 00000000..43d4675e --- /dev/null +++ b/packages/euclid/src/utils/pagination.rs @@ -0,0 +1,23 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct Pagination { + pub min: Option, + pub max: Option, + pub skip: Option, + pub limit: Option, +} +pub const DEFAULT_PAGINATION_LIMIT: u64 = 10; +pub const DEFAULT_PAGINATION_SKIP: u64 = 0; + +impl Pagination { + // Creates a new instance of Pagination + pub fn new(min: Option, max: Option, skip: Option, limit: Option) -> Self { + Pagination { + min, + max, + skip, + limit, + } + } +} diff --git a/packages/euclid/src/utils.rs b/packages/euclid/src/utils/tx.rs similarity index 62% rename from packages/euclid/src/utils.rs rename to packages/euclid/src/utils/tx.rs index 2c75b0e7..a7646dd7 100644 --- a/packages/euclid/src/utils.rs +++ b/packages/euclid/src/utils/tx.rs @@ -1,4 +1,3 @@ -use cosmwasm_schema::cw_serde; use cosmwasm_std::{DepsMut, Env}; use cw_storage_plus::Item; @@ -24,23 +23,3 @@ pub fn generate_tx( TX_NONCE.save(deps.storage, &nonce)?; Ok(format!("{sender}:{chain_id}:{height}:{index}:{nonce}")) } - -#[cw_serde] -pub struct Pagination { - pub min: Option, - pub max: Option, - pub skip: Option, - pub limit: Option, -} - -impl Pagination { - // Creates a new instance of Pagination - pub fn new(min: Option, max: Option, skip: Option, limit: Option) -> Self { - Pagination { - min, - max, - skip, - limit, - } - } -} diff --git a/packages/euclid/src/virtual_balance.rs b/packages/euclid/src/virtual_balance.rs index 436ef39f..12e6ec83 100644 --- a/packages/euclid/src/virtual_balance.rs +++ b/packages/euclid/src/virtual_balance.rs @@ -1,10 +1,8 @@ -use cosmwasm_schema::cw_serde; - use crate::{ chain::{ChainUid, CrossChainUser}, error::ContractError, }; - +use cosmwasm_schema::cw_serde; type AnyChainAddress = String; type TokenId = String; // Balance is stored again Chain Id, Address of the user on any chain, and for a specific Token Id diff --git a/packages/euclid_ibc/Cargo.toml b/packages/euclid_ibc/Cargo.toml index f9fa1a9f..fec4ee8f 100644 --- a/packages/euclid_ibc/Cargo.toml +++ b/packages/euclid_ibc/Cargo.toml @@ -1,6 +1,10 @@ [package] name = "euclid-ibc" -version = "0.1.0" +version = "0.2.0" +authors = [ + "gachouchani1999 ", + "Anshudhar Kumar Singh ", +] edition = "2021" [features] diff --git a/packages/euclid_ibc/src/msg.rs b/packages/euclid_ibc/src/msg.rs index 2084b707..523c4196 100644 --- a/packages/euclid_ibc/src/msg.rs +++ b/packages/euclid_ibc/src/msg.rs @@ -10,7 +10,7 @@ use euclid::{ error::ContractError, msgs::{factory, router}, swap::NextSwapPair, - token::{Pair, Token}, + token::{Pair, PairWithDenomAndAmount, Token, TokenWithDenom}, }; // Message that implements an ExecuteSwap on the VLP contract @@ -28,26 +28,31 @@ pub enum ChainIbcExecuteMsg { // Factory will set this using info.sender sender: CrossChainUser, tx_id: String, - pair: Pair, + pair: PairWithDenomAndAmount, + // User will provide this data + slippage_tolerance_bps: u64, }, - // Request Pool Creation - RequestEscrowCreation { + // Register Denom for a token + RegisterDenom { sender: CrossChainUser, tx_id: String, - token: Token, + token: TokenWithDenom, + }, + + // Register Denom for a token + DeRegisterDenom { + sender: CrossChainUser, + tx_id: String, + token: TokenWithDenom, }, AddLiquidity { // Factory will set this using info.sender sender: CrossChainUser, - // User will provide this data and factory will verify using info funds - token_1_liquidity: Uint128, - token_2_liquidity: Uint128, - // User will provide this data - slippage_tolerance: u64, + slippage_tolerance_bps: u64, - pair: Pair, + pair: PairWithDenomAndAmount, // Unique per tx tx_id: String, @@ -61,27 +66,11 @@ pub enum ChainIbcExecuteMsg { // Withdraw virtual balance message sent from factory Withdraw(ChainIbcWithdrawExecuteMsg), - // RequestWithdraw { - // token_id: Token, - // amount: Uint128, - - // // Factory will set this using info.sender - // sender: String, - - // // First element in array has highest priority - // cross_chain_addresses: Vec, - - // // Unique per tx - // tx_id: String, - // }, - // RequestEscrowCreation { - // token: Token, - // // Factory will set this using info.sender - // sender: String, - // // Unique per tx - // tx_id: String, - // //TODO Add allowed denoms? - // }, + + // Transfer virtual balance message sent from factory + Transfer(ChainIbcTransferExecuteMsg), + + DepositToken(ChainIbcDepositTokenExecuteMsg), } impl ChainIbcExecuteMsg { @@ -92,10 +81,14 @@ impl ChainIbcExecuteMsg { Self::RemoveLiquidity(msg) => msg.tx_id.clone(), Self::Swap(msg) => msg.tx_id.clone(), Self::Withdraw(msg) => msg.tx_id.clone(), - Self::RequestEscrowCreation { tx_id, .. } => tx_id.clone(), + Self::DepositToken(msg) => msg.tx_id.clone(), + Self::RegisterDenom { tx_id, .. } => tx_id.clone(), + Self::DeRegisterDenom { tx_id, .. } => tx_id.clone(), + Self::Transfer(msg) => msg.tx_id.clone(), } } + #[allow(clippy::too_many_arguments)] pub fn to_msg( &self, deps: &mut DepsMut, @@ -150,13 +143,10 @@ impl ChainIbcExecuteMsg { pub struct ChainIbcRemoveLiquidityExecuteMsg { // Factory will set this using info.sender pub sender: CrossChainUser, - pub lp_allocation: Uint128, pub pair: Pair, - // First element in array has highest priority pub cross_chain_addresses: Vec, - // Unique per tx pub tx_id: String, } @@ -167,7 +157,7 @@ pub struct ChainIbcSwapExecuteMsg { pub sender: CrossChainUser, // User will provide this - pub asset_in: Token, + pub asset_in: TokenWithDenom, pub amount_in: Uint128, pub asset_out: Token, pub min_amount_out: Uint128, @@ -175,6 +165,8 @@ pub struct ChainIbcSwapExecuteMsg { // First element in array has highest priority pub cross_chain_addresses: Vec, + pub partner_fee_amount: Uint128, + pub partner_fee_recipient: CrossChainUser, // Unique per tx pub tx_id: String, @@ -194,6 +186,31 @@ pub struct ChainIbcWithdrawExecuteMsg { pub timeout: Option, } +#[cw_serde] +pub struct ChainIbcTransferExecuteMsg { + // Factory will set this to info.sender + pub sender: CrossChainUser, + // User will provide this + pub token: Token, + pub amount: Uint128, + pub recipient_address: CrossChainUser, + // Unique per tx + pub tx_id: String, + pub timeout: Option, +} + +#[cw_serde] +pub struct ChainIbcDepositTokenExecuteMsg { + // Factory will set this to info.sender + pub sender: CrossChainUser, + // User will provide this + pub asset_in: Token, + pub amount_in: Uint128, + pub recipient: CrossChainUser, + // Unique per tx + pub tx_id: String, +} + pub const HUB_IBC_EXECUTE_MSG_QUEUE: Map = Map::new("hub_ibc_execute_msg_queue"); pub const HUB_IBC_EXECUTE_MSG_QUEUE_COUNT: Item = Item::new("hub_ibc_execute_msg_queue_count"); @@ -215,12 +232,10 @@ pub enum HubIbcExecuteMsg { }, ReleaseEscrow { - chain_uid: ChainUid, sender: CrossChainUser, amount: Uint128, + recipient: CrossChainUserWithLimit, token: Token, - to_address: String, - // Unique per tx tx_id: String, }, diff --git a/packages/euclid_utils/Cargo.toml b/packages/euclid_utils/Cargo.toml new file mode 100644 index 00000000..af7f3f81 --- /dev/null +++ b/packages/euclid_utils/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "euclid-utils" +version = "0.1.0" +edition = "2021" + + +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-schema = { workspace = true } +serde = { workspace = true } +schemars = { workspace = true } + +euclid = { workspace = true } diff --git a/packages/euclid_utils/src/lib.rs b/packages/euclid_utils/src/lib.rs new file mode 100644 index 00000000..c6d7262a --- /dev/null +++ b/packages/euclid_utils/src/lib.rs @@ -0,0 +1 @@ +pub mod msgs; diff --git a/packages/euclid_utils/src/msgs/mod.rs b/packages/euclid_utils/src/msgs/mod.rs new file mode 100644 index 00000000..c25dc9c7 --- /dev/null +++ b/packages/euclid_utils/src/msgs/mod.rs @@ -0,0 +1 @@ +pub mod multicall; diff --git a/packages/euclid_utils/src/msgs/multicall.rs b/packages/euclid_utils/src/msgs/multicall.rs new file mode 100644 index 00000000..1e2323f1 --- /dev/null +++ b/packages/euclid_utils/src/msgs/multicall.rs @@ -0,0 +1,38 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Empty, QueryRequest, QueryResponse}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +#[derive(QueryResponses)] + +pub enum QueryMsg { + #[returns(MultiQueryResponse)] + MultiQuery { queries: Vec }, +} + +#[cw_serde] +pub enum MultiQuery { + Query(QueryRequest), + RawQuery(String), +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct MultiQueryResponse { + pub responses: Vec, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct SingleQueryResponse { + pub result: Option, + pub err: Option, +} + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/packages/forwarding/Cargo.toml b/packages/forwarding/Cargo.toml new file mode 100644 index 00000000..305de43f --- /dev/null +++ b/packages/forwarding/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "forwarding" +version = "0.1.0" +edition = "2021" + + +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-schema = { workspace = true } +serde = { workspace = true } +schemars = { workspace = true } +cw20 = { workspace = true } + +euclid = { workspace = true } + +astroport = { git = "https://github.com/astroport-fi/astroport-core", branch = "main" } +cw-orch = "=0.24.1" diff --git a/packages/forwarding/src/lib.rs b/packages/forwarding/src/lib.rs new file mode 100644 index 00000000..c6d7262a --- /dev/null +++ b/packages/forwarding/src/lib.rs @@ -0,0 +1 @@ +pub mod msgs; diff --git a/packages/forwarding/src/msgs/astroport.rs b/packages/forwarding/src/msgs/astroport.rs new file mode 100644 index 00000000..b502c6b0 --- /dev/null +++ b/packages/forwarding/src/msgs/astroport.rs @@ -0,0 +1,32 @@ +use astroport::router::SwapOperation; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use cw20::Cw20ReceiveMsg; +use euclid::{msgs::hook::EuclidReceive, token::TokenType}; + +#[cw_serde] +pub struct InstantiateMsg { + pub astro_router: Addr, +} + +#[cw_serde] +#[derive(cw_orch::ExecuteFns)] +pub enum ExecuteMsg { + Receive(Cw20ReceiveMsg), + EuclidReceive(EuclidReceive), + Swap(SwapMsg), +} + +#[cw_serde] +#[derive(cw_orch::QueryFns, QueryResponses)] +pub enum QueryMsg {} + +#[cw_serde] +pub struct SwapMsg { + pub operations: Vec, + pub max_spread: Option, + pub forwarding_msg: Option, + pub to_token: TokenType, + pub minimum_receive: Uint128, + pub recipient: String, +} diff --git a/packages/forwarding/src/msgs/cw20.rs b/packages/forwarding/src/msgs/cw20.rs new file mode 100644 index 00000000..97c0f31f --- /dev/null +++ b/packages/forwarding/src/msgs/cw20.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::cw_serde; +use euclid::msgs::hook::EuclidReceive; + +use super::astroport::SwapMsg; + +#[cw_serde] +pub enum Cw20HookMsg { + EuclidReceive(EuclidReceive), + Swap(SwapMsg), +} diff --git a/packages/forwarding/src/msgs/euclid_receive.rs b/packages/forwarding/src/msgs/euclid_receive.rs new file mode 100644 index 00000000..033fe556 --- /dev/null +++ b/packages/forwarding/src/msgs/euclid_receive.rs @@ -0,0 +1,8 @@ +use cosmwasm_schema::cw_serde; + +use super::astroport::SwapMsg; + +#[cw_serde] +pub enum AstroportEuclidReceiveHook { + Swap(SwapMsg), +} diff --git a/packages/forwarding/src/msgs/mod.rs b/packages/forwarding/src/msgs/mod.rs new file mode 100644 index 00000000..66deefa9 --- /dev/null +++ b/packages/forwarding/src/msgs/mod.rs @@ -0,0 +1,3 @@ +pub mod astroport; +pub mod cw20; +pub mod euclid_receive; diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml index 904f4020..6630b0dd 100644 --- a/tests-integration/Cargo.toml +++ b/tests-integration/Cargo.toml @@ -14,6 +14,7 @@ virtual_balance = { path = "../contracts/hub/virtual_balance" } vlp = { path = "../contracts/hub/vlp" } escrow = { path = "../contracts/liquidity/escrow" } factory = { path = "../contracts/liquidity/factory" } +cw20 = { path = "../contracts/liquidity/cw20" } #Other Crates @@ -26,7 +27,9 @@ cw-storage-plus = { workspace = true } itertools = { workspace = true } cosmwasm-schema = { workspace = true } cw-utils = { workspace = true } - +cw-orch = "=0.24.1" +cw-orch-interchain = "=0.3.0" +ibc-relayer-types = "=0.25.1" anyhow = "1.0.79" diff --git a/tests-integration/src/helpers/chains.rs b/tests-integration/src/helpers/chains.rs new file mode 100644 index 00000000..0fee4ee1 --- /dev/null +++ b/tests-integration/src/helpers/chains.rs @@ -0,0 +1,126 @@ +#![cfg(not(target_arch = "wasm32"))] +use cw20::Cw20Contract; +use cw_orch::{mock::MockBase, prelude::*}; +use cw_orch_interchain::{InterchainEnv, MockInterchainEnv}; +use escrow::EscrowContract; +use euclid::{ + chain::ChainUid, + msgs::{ + factory::ExecuteMsgFns as FactoryExecuteMsgFns, + router::{ + ExecuteMsgFns as RouterExecuteMsgFns, QueryMsgFns, RegisterFactoryChainIbc, + RegisterFactoryChainNative, + }, + }, +}; +use factory::FactoryContract; +use router::RouterContract; +use virtual_balance::VirtualBalanceContract; +use vlp::VlpContract; + +pub fn setup_factory( + interchain: &MockInterchainEnv, + factory_chain_id: &str, + router_chain_id: &str, + router: &RouterContract, +) -> FactoryContract { + let chain_uid = ChainUid::create(factory_chain_id.to_string()).unwrap(); + let chain = interchain.get_chain(factory_chain_id).unwrap(); + let _router_chain = interchain.get_chain(router_chain_id).unwrap(); + let factory = FactoryContract::new(chain.clone()); + let escrow = EscrowContract::new(chain.clone()); + let cw20 = Cw20Contract::new(chain.clone()); + + factory.upload().unwrap(); + escrow.upload().unwrap(); + cw20.upload().unwrap(); + + factory + .instantiate( + &euclid::msgs::factory::InstantiateMsg { + router_contract: router.address().unwrap().to_string(), + chain_uid: chain_uid.clone(), + escrow_code_id: escrow.code_id().unwrap(), + cw20_code_id: cw20.code_id().unwrap(), + is_native: false, + }, + None, + None, + ) + .unwrap(); + + if router_chain_id != factory_chain_id { + // Set up channel from osmosis to nibiru + let channel_receipt = interchain + .create_contract_channel(&factory, router, "counter-1", None) + .unwrap(); + let factory_channel = channel_receipt + .interchain_channel + .get_chain(factory_chain_id) + .unwrap() + .channel + .unwrap(); + + let router_channel = channel_receipt + .interchain_channel + .get_chain(router_chain_id) + .unwrap() + .channel + .unwrap(); + + factory + .update_hub_channel(factory_channel.to_string()) + .unwrap(); + + let chain_info = + euclid::msgs::router::RegisterFactoryChainType::Ibc(RegisterFactoryChainIbc { + channel: router_channel.to_string(), + timeout: None, + }); + let register_request = router + .register_factory(chain_info, chain_uid.clone()) + .unwrap(); + let _ = interchain + .await_packets(router_chain_id, register_request) + .unwrap(); + } else { + let chain_info = + euclid::msgs::router::RegisterFactoryChainType::Native(RegisterFactoryChainNative { + factory_address: factory.address().unwrap().to_string(), + }); + router + .register_factory(chain_info, chain_uid.clone()) + .unwrap(); + } + let all_chains = router.get_all_chains().unwrap(); + // Asert that this chain is registered + assert!(all_chains + .chains + .iter() + .any(|c| c.chain_uid == chain_uid.clone())); + + factory +} + +pub fn setup_router(chain: &MockBase) -> RouterContract { + let router = RouterContract::new(chain.clone()); + let virtual_balance = VirtualBalanceContract::new(chain.clone()); + let vlp = VlpContract::new(chain.clone()); + + router.upload().unwrap(); + virtual_balance.upload().unwrap(); + vlp.upload().unwrap(); + + router + .instantiate( + &euclid::msgs::router::InstantiateMsg { + vlp_code_id: vlp.code_id().unwrap(), + virtual_balance_code_id: virtual_balance.code_id().unwrap(), + }, + None, + None, + ) + .unwrap(); + + router +} diff --git a/tests-integration/src/helpers/factory.rs b/tests-integration/src/helpers/factory.rs new file mode 100644 index 00000000..f7881280 --- /dev/null +++ b/tests-integration/src/helpers/factory.rs @@ -0,0 +1,145 @@ +#![cfg(not(target_arch = "wasm32"))] + +use cosmwasm_std::coin; +use cw20::Cw20Contract; +use cw_orch::mock::MockBase; +use cw_orch::prelude::*; + +use cw_orch_interchain::IbcQueryHandler; +use euclid::msgs::cw20::ExecuteMsgFns; +use euclid::msgs::factory::{ + ExecuteMsgFns as FactoryExecuteMsgFns, QueryMsgFns as FactoryQueryMsgFns, +}; + +use cw_orch_interchain::InterchainEnv; +use cw_orch_interchain::MockInterchainEnv; +use euclid::token::PairWithDenomAndAmount; +use euclid::token::TokenType; +use euclid::token::TokenWithDenom; +use factory::FactoryContract; + +pub fn register_token( + interchain: &MockInterchainEnv, + factory: &FactoryContract, + token: TokenWithDenom, +) { + let tx_response = factory.request_register_denom(token.clone(), None).unwrap(); + + let _ = interchain + .await_packets(factory.environment().chain_id().as_str(), tx_response) + .unwrap(); + + let escrow_response = factory.get_escrow(token.token.to_string()); + assert!(escrow_response.is_ok(), "Escrow not registered"); + + assert!( + escrow_response + .unwrap() + .denoms + .iter() + .any(|d| d == &token.token_type), + "Escrow found but denom not registered" + ); +} + +pub fn faucet( + chain: &MockBase, + address: &str, + amount: u128, + token_type: TokenType, + funds: &mut Vec, +) { + match token_type { + TokenType::Native { denom } => { + chain + .add_balance(address, vec![coin(amount, denom.clone())]) + .unwrap(); + // attach native token to the message + funds.push(coin(amount, denom)); + } + TokenType::Smart { contract_address } => { + let cw20 = Cw20Contract::new(chain.clone()); + cw20.set_address(&Addr::unchecked(contract_address)); + // Increase allowance + cw20.increase_allowance(amount, address, None).unwrap(); + } + _ => {} + }; +} + +pub fn create_pool( + interchain: &MockInterchainEnv, + factory: &FactoryContract, + pair_with_denom: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, +) { + let chain = interchain + .get_chain(factory.environment().chain_id().as_str()) + .unwrap(); + let mut funds = vec![]; + for token in pair_with_denom.get_vec_token_info() { + faucet( + &chain, + chain.sender.as_str(), + token.amount.u128(), + token.token_type.clone(), + &mut funds, + ); + } + let tx_response = factory + .execute( + &euclid::msgs::factory::ExecuteMsg::RequestPoolCreation { + pair: pair_with_denom.clone(), + slippage_tolerance_bps, + timeout: None, + lp_token_name: "LPNAME".to_string(), + lp_token_symbol: "LPSYMBOL".to_string(), + lp_token_decimal: 6, + lp_token_marketing: None, + }, + Some(&funds), + ) + .unwrap(); + + let _ = interchain + .await_packets(factory.environment().chain_id().as_str(), tx_response) + .unwrap(); + + let registered_pool = factory.get_vlp(pair_with_denom.get_pair().unwrap()); + assert!(registered_pool.is_ok(), "Pool not registered"); +} + +pub fn add_liquidity( + interchain: &MockInterchainEnv, + factory: &FactoryContract, + pair_with_denom: PairWithDenomAndAmount, + slippage_tolerance_bps: u64, +) { + let chain = interchain + .get_chain(factory.environment().chain_id().as_str()) + .unwrap(); + let mut funds = vec![]; + for token in pair_with_denom.get_vec_token_info() { + faucet( + &chain, + chain.sender.as_str(), + token.amount.u128(), + token.token_type.clone(), + &mut funds, + ); + } + let tx_response = factory + .execute( + &euclid::msgs::factory::ExecuteMsg::AddLiquidityRequest { + pair_info: pair_with_denom.clone(), + slippage_tolerance_bps, + timeout: None, + }, + Some(&funds), + ) + .unwrap(); + + let _ = interchain + .await_packets(factory.environment().chain_id().as_str(), tx_response) + .unwrap(); +} diff --git a/tests-integration/src/helpers/mod.rs b/tests-integration/src/helpers/mod.rs new file mode 100644 index 00000000..529b61d1 --- /dev/null +++ b/tests-integration/src/helpers/mod.rs @@ -0,0 +1,4 @@ +#![cfg(not(target_arch = "wasm32"))] + +pub mod chains; +pub mod factory; diff --git a/tests-integration/src/lib.rs b/tests-integration/src/lib.rs index eb79ed88..8371cf42 100644 --- a/tests-integration/src/lib.rs +++ b/tests-integration/src/lib.rs @@ -2,3 +2,6 @@ #[cfg(test)] mod tests; + +#[cfg(test)] +mod helpers; diff --git a/tests-integration/src/tests/escrow.rs b/tests-integration/src/tests/escrow.rs index 727aec6e..1ab57df2 100644 --- a/tests-integration/src/tests/escrow.rs +++ b/tests-integration/src/tests/escrow.rs @@ -1,67 +1,56 @@ #![cfg(not(target_arch = "wasm32"))] -use cosmwasm_std::coin; -use escrow::mock::{mock_escrow, MockEscrow}; -use euclid::{ - chain::ChainUid, - msgs::escrow::TokenIdResponse, - token::{Token, TokenType}, -}; -use factory::mock::mock_factory; -use factory::mock::MockFactory; -use mock::{mock::mock_app, mock_builder::MockEuclidBuilder}; - +use cosmwasm_std::Addr; +use cw_orch::prelude::*; +use escrow::EscrowContract; +use euclid::msgs::escrow::AllowedDenomsResponse; +use euclid::msgs::escrow::QueryMsgFns; +use euclid::msgs::escrow::{ExecuteMsgFns, InstantiateMsg}; +use euclid::token::{Token, TokenType}; const _USER: &str = "user"; const _NATIVE_DENOM: &str = "native"; const _IBC_DENOM_1: &str = "ibc/denom1"; const _IBC_DENOM_2: &str = "ibc/denom2"; const _SUPPLY: u128 = 1_000_000; +use cw_orch_interchain::prelude::*; +use cw_orch_interchain::InterchainEnv; #[test] -fn test_proper_instantiation() { - let mut escrow = mock_app(None); - let andr = MockEuclidBuilder::new(&mut escrow, "admin") - .with_wallets(vec![ - ("owner", vec![coin(1000, "eucl")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ]) - .with_contracts(vec![("escrow", mock_escrow()), ("factory", mock_factory())]) - .build(&mut escrow); - let owner = andr.get_wallet("owner"); +fn test_escrow() { + // Here `juno-1` is the chain-id and `juno` is the address prefix for this chain + let mut interchain = + MockBech32InterchainEnv::new(vec![("juno-1", "juno"), ("osmosis-1", "osmo")]); + let _local_juno = interchain.get_chain("juno-1").unwrap(); + let _local_osmo = interchain.get_chain("osmosis-1").unwrap(); + let test_migaloo = MockBech32::new_with_chain_id("migaloo-1", "migaloo"); + interchain.add_mocks(vec![test_migaloo]); - let escrow_code_id = 1; - let factory_code_id = 2; - let cw20_code_id = 3; - let chain_uid = ChainUid::create("chain1".to_string()).unwrap(); - let router_contract = "router_contract".to_string(); + let sender = Addr::unchecked("juno16g2rahf5846rxzp3fwlswy08fz8ccuwk03k57y"); - let token_id = Token::create("token1".to_string()).unwrap(); - let allowed_denom = Some(TokenType::Native { - denom: "eucl".to_string(), - }); + let mock = Mock::new(sender); + let escrow_contract = EscrowContract::new(mock); - let mock_factory = MockFactory::instantiate( - &mut escrow, - factory_code_id, - owner.clone(), - router_contract, - chain_uid, - escrow_code_id, - cw20_code_id, - true, - ); + let upload_res = escrow_contract.upload(); + upload_res.unwrap(); - let mock_escrow = MockEscrow::instantiate( - &mut escrow, - escrow_code_id, - mock_factory.addr().clone(), - token_id.clone(), - allowed_denom, - ); + let _res = escrow_contract + .instantiate( + &InstantiateMsg { + token_id: Token::create("token".to_string()).unwrap(), + allowed_denom: None, + }, + None, + None, + ) + .unwrap(); - let token_id_response = MockEscrow::query_token_id(&mock_escrow, &mut escrow); - let expected_token_id = TokenIdResponse { - token_id: token_id.to_string(), + let native_denom = TokenType::Native { + denom: "native".to_string(), }; - assert_eq!(token_id_response, expected_token_id); + + escrow_contract + .add_allowed_denom(native_denom.clone()) + .unwrap(); + + let allowed_denoms: AllowedDenomsResponse = escrow_contract.allowed_denoms().unwrap(); + assert_eq!(allowed_denoms.denoms, vec![native_denom]); } diff --git a/tests-integration/src/tests/factory.rs b/tests-integration/src/tests/factory.rs index 86323c89..f0ef49e5 100644 --- a/tests-integration/src/tests/factory.rs +++ b/tests-integration/src/tests/factory.rs @@ -1,19 +1,43 @@ #![cfg(not(target_arch = "wasm32"))] use std::collections::HashMap; -use cosmwasm_std::coin; -use escrow::mock::mock_escrow; -use euclid::fee::DenomFees; -use euclid::{chain::ChainUid, msgs::factory::StateResponse}; -use factory::mock::mock_factory; -use factory::mock::MockFactory; +use cosmwasm_std::{coin, Addr, Coin, Uint128}; +use cw20::Cw20Contract; +use cw_orch::prelude::{ + ContractInstance, CwOrchExecute, CwOrchInstantiate, CwOrchQuery, CwOrchUpload, +}; +use cw_orch_interchain::{prelude::*, types::IbcPacketOutcome, InterchainEnv}; +use escrow::{mock::mock_escrow, EscrowContract}; +use euclid::{ + chain::{ChainUid, CrossChainUser, CrossChainUserWithLimit}, + error::ContractError, + fee::{DenomFees, BPS_1_PERCENT}, + msgs::{ + escrow::StateResponse as EscrowStateResponse, + factory::{ + AllPoolsResponse, ExecuteMsgFns, ExecuteSwapRequest, PoolVlpResponse, StateResponse, + }, + router::{ + RegisterFactoryChainIbc, RegisterFactoryChainNative, TokenDenom, TokenDenomsResponse, + VlpResponse, + }, + virtual_balance::{GetBalanceResponse, GetStateResponse}, + vlp::GetLiquidityResponse, + }, + swap::NextSwapPair, + token::{Pair, PairWithDenomAndAmount, Token, TokenWithDenom, TokenWithDenomAndAmount}, + virtual_balance::BalanceKey, +}; +use factory::{ + mock::{mock_factory, MockFactory}, + FactoryContract, +}; use mock::{mock::mock_app, mock_builder::MockEuclidBuilder}; +use router::RouterContract; +use virtual_balance::VirtualBalanceContract; +use vlp::VlpContract; -const _USER: &str = "user"; -const _NATIVE_DENOM: &str = "native"; -const _IBC_DENOM_1: &str = "ibc/denom1"; -const _IBC_DENOM_2: &str = "ibc/denom2"; -const _SUPPLY: u128 = 1_000_000; +use crate::helpers::factory::{add_liquidity, create_pool, register_token}; #[test] fn test_proper_instantiation() { @@ -45,7 +69,7 @@ fn test_proper_instantiation() { true, ); - let state_response = MockFactory::query_state(&mock_factory, &mut factory); + let state_response = MockFactory::query_state(&mock_factory, &factory); let expected_state_id = StateResponse { chain_uid, router_contract, @@ -60,3 +84,807 @@ fn test_proper_instantiation() { }; assert_eq!(state_response, expected_state_id); } + +#[test] +fn test_create_pool_with_funds() { + let sender = Addr::unchecked("sender_for_all_chains").into_string(); + let interchain = MockInterchainEnv::new(vec![("osmosis", &sender), ("nibiru", &sender)]); + let osmosis = interchain.get_chain("osmosis").unwrap(); + let nibiru = interchain.get_chain("nibiru").unwrap(); + + osmosis + .set_balance( + sender.clone(), + vec![ + Coin::new(100000000000000, "osmo"), + Coin::new(100000000000000, "eucl"), + ], + ) + .unwrap(); + + nibiru + .set_balance( + sender.clone(), + vec![ + Coin::new(100000000000000, "nibi"), + Coin::new(100000000000000, "eucl"), + ], + ) + .unwrap(); + + let factory_osmosis = FactoryContract::new(osmosis.clone()); + let escrow_osmosis = EscrowContract::new(osmosis.clone()); + let cw20_osmosis = Cw20Contract::new(osmosis.clone()); + let router_nibiru = RouterContract::new(nibiru.clone()); + let virtual_balance_nibiru = VirtualBalanceContract::new(nibiru.clone()); + let vlp_nibiru = VlpContract::new(nibiru.clone()); + + factory_osmosis.upload().unwrap(); + escrow_osmosis.upload().unwrap(); + cw20_osmosis.upload().unwrap(); + router_nibiru.upload().unwrap(); + virtual_balance_nibiru.upload().unwrap(); + vlp_nibiru.upload().unwrap(); + + router_nibiru + .instantiate( + &euclid::msgs::router::InstantiateMsg { + vlp_code_id: 3, + virtual_balance_code_id: 2, + }, + None, + None, + ) + .unwrap(); + + factory_osmosis + .instantiate( + &euclid::msgs::factory::InstantiateMsg { + router_contract: router_nibiru.address().unwrap().into_string(), + chain_uid: ChainUid::create("osmosis".to_string()).unwrap(), + escrow_code_id: 2, + cw20_code_id: 3, + is_native: false, + }, + None, + None, + ) + .unwrap(); + + // Set up channel from osmosis to nibiru + let channel_receipt = interchain + .create_contract_channel(&factory_osmosis, &router_nibiru, "counter-1", None) + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + let osmosis_channel = channel_receipt + .interchain_channel + .get_chain("osmosis") + .unwrap() + .channel + .unwrap(); + + // Update Hub Channel + factory_osmosis + .update_hub_channel(osmosis_channel.to_string()) + .unwrap(); + + let register_factory_request = router_nibiru + .execute( + &euclid::msgs::router::ExecuteMsg::RegisterFactory { + chain_uid: ChainUid::create("osmosis".to_string()).unwrap(), + chain_info: euclid::msgs::router::RegisterFactoryChainType::Ibc( + RegisterFactoryChainIbc { + channel: osmosis_channel.to_string(), + timeout: None, + }, + ), + }, + None, + ) + .unwrap(); + + let _ = interchain + .await_packets("nibiru", register_factory_request) + .unwrap(); + + // // Register escrow + let register_escrow_request = factory_osmosis + .execute( + &euclid::msgs::factory::ExecuteMsg::RequestRegisterDenom { + token: TokenWithDenom { + token: Token::create("osmo".to_string()).unwrap(), + token_type: euclid::token::TokenType::Native { + denom: "osmo".to_string(), + }, + }, + timeout: None, + }, + None, + ) + .unwrap(); + + let _ = interchain + .await_packets("osmosis", register_escrow_request) + .unwrap(); + + let token_denoms_response: TokenDenomsResponse = router_nibiru + .query(&euclid::msgs::router::QueryMsg::QueryTokenDenoms { + token: Token::create("osmo".to_string()).unwrap(), + }) + .unwrap(); + + assert_eq!( + token_denoms_response, + TokenDenomsResponse { + denoms: vec![TokenDenom { + chain_uid: ChainUid::create("osmosis".to_string()).unwrap(), + token_type: euclid::token::TokenType::Native { + denom: "osmo".to_string(), + }, + }], + } + ); + + // Test Create pool without funds + let create_pool_with_funds_request = factory_osmosis.execute( + &euclid::msgs::factory::ExecuteMsg::RequestPoolCreation { + pair: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(0u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("osmo".to_string()).unwrap(), + amount: Uint128::from(0u128), + token_type: euclid::token::TokenType::Native { + denom: "osmo".to_string(), + }, + }, + }, + slippage_tolerance_bps: 100, + timeout: None, + lp_token_name: "osmosis".to_string(), + lp_token_symbol: "osmo".to_string(), + lp_token_decimal: 6, + lp_token_marketing: None, + }, + None, // Some(&[coin(0u128, "osmo"), coin(0u128, "eucl")]), + ); + assert_eq!( + ContractError::new("Amount cannot be zero"), + create_pool_with_funds_request + .unwrap_err() + .downcast() + .unwrap() + ); + + // Need to request register escrow first + let create_pool_with_funds_request = factory_osmosis + .execute( + &euclid::msgs::factory::ExecuteMsg::RequestPoolCreation { + pair: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(10_000u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("osmo".to_string()).unwrap(), + amount: Uint128::from(100_000u128), + token_type: euclid::token::TokenType::Native { + denom: "osmo".to_string(), + }, + }, + }, + slippage_tolerance_bps: 100, + timeout: None, + lp_token_name: "osmosis".to_string(), + lp_token_symbol: "osmo".to_string(), + lp_token_decimal: 6, + lp_token_marketing: None, + }, + Some(&[coin(100_000u128, "osmo"), coin(10_000u128, "eucl")]), + ) + .unwrap(); + + let packet_lifetime = interchain + .await_packets("osmosis", create_pool_with_funds_request) + .unwrap(); + + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + } else { + panic!("packet timed out"); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application + }; + + let all_pools_query: AllPoolsResponse = factory_osmosis + .query(&euclid::msgs::factory::QueryMsg::GetAllPools {}) + .unwrap(); + assert_eq!( + all_pools_query, + AllPoolsResponse { + pools: vec![PoolVlpResponse { + pair: Pair::new( + Token::create("eucl".to_string()).unwrap(), + Token::create("osmo".to_string()).unwrap(), + ) + .unwrap(), + vlp: Addr::unchecked("contract2").into_string(), + }], + } + ); + + let vlp_query: VlpResponse = router_nibiru + .query(&euclid::msgs::router::QueryMsg::GetVlp { + pair: Pair::new( + Token::create("osmo".to_string()).unwrap(), + Token::create("eucl".to_string()).unwrap(), + ) + .unwrap(), + }) + .unwrap(); + assert_eq!( + vlp_query, + VlpResponse { + vlp: Addr::unchecked("contract2").into_string(), + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("osmo".to_string()).unwrap(), + } + ); + + // Got this address from the query above + vlp_nibiru.set_address(&Addr::unchecked("contract2")); + + let liquidity_query: GetLiquidityResponse = vlp_nibiru + .query(&euclid::msgs::vlp::QueryMsg::Liquidity {}) + .unwrap(); + assert_eq!( + liquidity_query, + GetLiquidityResponse { + pair: Pair { + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("osmo".to_string()).unwrap(), + }, + token_1_reserve: Uint128::new(10_000), + token_2_reserve: Uint128::new(100_000), + total_lp_tokens: Uint128::new(30622), + } + ); + virtual_balance_nibiru.set_address(&Addr::unchecked("contract1")); + + let _vbalance_query: GetStateResponse = virtual_balance_nibiru + .query(&euclid::msgs::virtual_balance::QueryMsg::GetState {}) + .unwrap(); + + // Osmo escrow contract + escrow_osmosis.set_address(&Addr::unchecked("contract1")); + let escrow_query: EscrowStateResponse = escrow_osmosis + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("osmo".to_string()).unwrap(), + factory_address: Addr::unchecked("contract0"), + total_amount: Uint128::from(100_000u128), + } + ); + + // This is the escrow for the Euclid token + escrow_osmosis.set_address(&Addr::unchecked("contract2")); + let escrow_query: EscrowStateResponse = escrow_osmosis + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("eucl".to_string()).unwrap(), + factory_address: Addr::unchecked("contract0"), + total_amount: Uint128::from(10_000u128), + } + ); + + // Add Liquidity + // Need to request register escrow first + let add_liquidity_request = factory_osmosis + .execute( + &euclid::msgs::factory::ExecuteMsg::AddLiquidityRequest { + pair_info: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(10_000u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("osmo".to_string()).unwrap(), + amount: Uint128::from(100_000u128), + token_type: euclid::token::TokenType::Native { + denom: "osmo".to_string(), + }, + }, + }, + slippage_tolerance_bps: 100, // 1% slippage tolerance + timeout: None, // 10 minutes in seconds + }, + Some(&[coin(100_000u128, "osmo"), coin(10_000u128, "eucl")]), + ) + .unwrap(); + + let packet_lifetime = interchain + .await_packets("osmosis", add_liquidity_request) + .unwrap(); + + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + } else { + panic!("packet timed out"); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application + }; + let liquidity_query: GetLiquidityResponse = vlp_nibiru + .query(&euclid::msgs::vlp::QueryMsg::Liquidity {}) + .unwrap(); + assert_eq!( + liquidity_query, + GetLiquidityResponse { + pair: Pair { + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("osmo".to_string()).unwrap(), + }, + token_1_reserve: Uint128::new(10_000u128 * 2), + token_2_reserve: Uint128::new(100_000u128 * 2), + total_lp_tokens: Uint128::new(30622u128 * 2), + } + ); + // Euclid escrow contract + let escrow_query: EscrowStateResponse = escrow_osmosis + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("eucl".to_string()).unwrap(), + factory_address: Addr::unchecked("contract0"), + total_amount: Uint128::from(10_000u128 * 2), + } + ); + // Osmo escrow contract + escrow_osmosis.set_address(&Addr::unchecked("contract1")); + let escrow_query: EscrowStateResponse = escrow_osmosis + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("osmo".to_string()).unwrap(), + factory_address: Addr::unchecked("contract0"), + total_amount: Uint128::from(100_000u128 * 2), + } + ); + + // Same chain test, need to upload liquidity contracts on Hub + let factory_nibiru = FactoryContract::new(nibiru.clone()); + let escrow_nibiru = EscrowContract::new(nibiru.clone()); + let cw20_nibiru = Cw20Contract::new(nibiru.clone()); + //4 + factory_nibiru.upload().unwrap(); + //5 + escrow_nibiru.upload().unwrap(); + //6 + cw20_nibiru.upload().unwrap(); + + factory_nibiru + .instantiate( + &euclid::msgs::factory::InstantiateMsg { + router_contract: router_nibiru.address().unwrap().into_string(), + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + escrow_code_id: 5, + cw20_code_id: 6, + is_native: true, + }, + None, + None, + ) + .unwrap(); + + router_nibiru + .execute( + &euclid::msgs::router::ExecuteMsg::RegisterFactory { + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + chain_info: euclid::msgs::router::RegisterFactoryChainType::Native( + RegisterFactoryChainNative { + factory_address: factory_nibiru.address().unwrap().into_string(), + }, + ), + }, + None, + ) + .unwrap(); + + factory_nibiru + .execute( + &euclid::msgs::factory::ExecuteMsg::RequestRegisterDenom { + token: TokenWithDenom { + token: Token::create("eucl".to_string()).unwrap(), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + timeout: None, + }, + None, + ) + .unwrap(); + + factory_nibiru + .execute( + &euclid::msgs::factory::ExecuteMsg::RequestPoolCreation { + pair: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("nibi".to_string()).unwrap(), + amount: Uint128::from(100_000u128), + token_type: euclid::token::TokenType::Native { + denom: "nibi".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(10_000u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + }, + slippage_tolerance_bps: 100, + timeout: None, + lp_token_name: "nibiru".to_string(), + lp_token_symbol: "nibi".to_string(), + lp_token_decimal: 6, + lp_token_marketing: None, + }, + Some(&[coin(100_000u128, "nibi"), coin(10_000u128, "eucl")]), + ) + .unwrap(); + + // Validation checks // + let all_pools_query: AllPoolsResponse = factory_nibiru + .query(&euclid::msgs::factory::QueryMsg::GetAllPools {}) + .unwrap(); + assert_eq!( + all_pools_query, + AllPoolsResponse { + pools: vec![PoolVlpResponse { + pair: Pair::new( + Token::create("eucl".to_string()).unwrap(), + Token::create("nibi".to_string()).unwrap(), + ) + .unwrap(), + vlp: Addr::unchecked("contract5").into_string(), + }], + } + ); + + let vlp_query: VlpResponse = router_nibiru + .query(&euclid::msgs::router::QueryMsg::GetVlp { + pair: Pair::new( + Token::create("nibi".to_string()).unwrap(), + Token::create("eucl".to_string()).unwrap(), + ) + .unwrap(), + }) + .unwrap(); + assert_eq!( + vlp_query, + VlpResponse { + vlp: Addr::unchecked("contract5").into_string(), + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("nibi".to_string()).unwrap(), + } + ); + + // Got this address from the query above + vlp_nibiru.set_address(&Addr::unchecked("contract5")); + + let liquidity_query: GetLiquidityResponse = vlp_nibiru + .query(&euclid::msgs::vlp::QueryMsg::Liquidity {}) + .unwrap(); + assert_eq!( + liquidity_query, + GetLiquidityResponse { + pair: Pair { + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("nibi".to_string()).unwrap(), + }, + token_1_reserve: Uint128::new(10_000), + token_2_reserve: Uint128::new(100_000), + total_lp_tokens: Uint128::new(30622), + } + ); + virtual_balance_nibiru.set_address(&Addr::unchecked("contract1")); + + let _vbalance_query: GetStateResponse = virtual_balance_nibiru + .query(&euclid::msgs::virtual_balance::QueryMsg::GetState {}) + .unwrap(); + + // Nibiru escrow contract + escrow_nibiru.set_address(&Addr::unchecked("contract6")); + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("nibi".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + total_amount: Uint128::from(100_000u128), + } + ); + + // This is the escrow for the Euclid token + escrow_nibiru.set_address(&Addr::unchecked("contract4")); + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("eucl".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + total_amount: Uint128::from(10_000u128), + } + ); + + // Add Liquidity + factory_nibiru + .execute( + &euclid::msgs::factory::ExecuteMsg::AddLiquidityRequest { + pair_info: PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(10_000u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("nibi".to_string()).unwrap(), + amount: Uint128::from(100_000u128), + token_type: euclid::token::TokenType::Native { + denom: "nibi".to_string(), + }, + }, + }, + slippage_tolerance_bps: 100, // 1% slippage tolerance + timeout: None, // 10 minutes in seconds + }, + Some(&[coin(100_000u128, "nibi"), coin(10_000u128, "eucl")]), + ) + .unwrap(); + + let liquidity_query: GetLiquidityResponse = vlp_nibiru + .query(&euclid::msgs::vlp::QueryMsg::Liquidity {}) + .unwrap(); + assert_eq!( + liquidity_query, + GetLiquidityResponse { + pair: Pair { + token_1: Token::create("eucl".to_string()).unwrap(), + token_2: Token::create("nibi".to_string()).unwrap(), + }, + token_1_reserve: Uint128::new(10_000u128 * 2), + token_2_reserve: Uint128::new(100_000u128 * 2), + total_lp_tokens: Uint128::new(30622u128 * 2), + } + ); + // Euclid escrow contract + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("eucl".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + total_amount: Uint128::from(10_000u128 * 2), + } + ); + // Osmo escrow contract + escrow_nibiru.set_address(&Addr::unchecked("contract6")); + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("nibi".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + total_amount: Uint128::from(100_000u128 * 2), + } + ); + // Test swap + let eucl_token = TokenWithDenom { + token: Token::create("eucl".to_string()).unwrap(), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }; + let nibi_token = TokenWithDenom { + token: Token::create("nibi".to_string()).unwrap(), + token_type: euclid::token::TokenType::Native { + denom: "nibi".to_string(), + }, + }; + factory_nibiru + .execute( + &euclid::msgs::factory::ExecuteMsg::ExecuteSwapRequest(ExecuteSwapRequest { + sender: None, + asset_in: eucl_token.clone(), + amount_in: Uint128::from(1_000u128), + asset_out: nibi_token.token.clone(), + min_amount_out: Uint128::from(9000u128), + timeout: None, + swaps: vec![NextSwapPair { + token_in: eucl_token.token.clone(), + token_out: nibi_token.token, + test_fail: None, + }], + cross_chain_addresses: vec![CrossChainUserWithLimit { + user: CrossChainUser { + address: sender.clone(), + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + }, + limit: None, + preferred_denom: None, + refund_address: None, + forwarding_message: None, + }], + partner_fee: None, + }), + Some(&[coin(1_000u128, "eucl")]), + ) + .unwrap(); + + // Check balances after swap + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("nibi".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + // Total amount decreased by 9506 + total_amount: Uint128::from((100_000u128 * 2) - 9506), + } + ); + // This is the escrow for the Euclid token + escrow_nibiru.set_address(&Addr::unchecked("contract4")); + let escrow_query: EscrowStateResponse = escrow_nibiru + .query(&euclid::msgs::escrow::QueryMsg::State {}) + .unwrap(); + assert_eq!( + escrow_query, + EscrowStateResponse { + token: Token::create("eucl".to_string()).unwrap(), + factory_address: Addr::unchecked("contract3"), + // Total amount increased by 1000 + total_amount: Uint128::from((10_000u128 * 2) + 1000), + } + ); + + // Test deposit + factory_nibiru + .execute( + &euclid::msgs::factory::ExecuteMsg::DepositToken { + amount_in: Uint128::from(100u128), + asset_in: eucl_token.clone(), + recipient: None, + timeout: None, + }, + Some(&[coin(100, "eucl")]), + ) + .unwrap(); + + let virtual_balance_query: GetBalanceResponse = virtual_balance_nibiru + .query(&euclid::msgs::virtual_balance::QueryMsg::GetBalance { + balance_key: BalanceKey { + cross_chain_user: CrossChainUser { + address: sender.clone(), + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + }, + token_id: eucl_token.token.to_string(), + }, + }) + .unwrap(); + assert_eq!( + virtual_balance_query, + GetBalanceResponse { + amount: Uint128::from(100u128), + } + ); + + // Test withdraw + factory_nibiru + .withdraw_virtual_balance( + Uint128::new(50), + vec![CrossChainUserWithLimit { + user: CrossChainUser { + address: sender.clone(), + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + }, + limit: None, + preferred_denom: None, + refund_address: None, + forwarding_message: None, + }], + Token::create("eucl".to_string()).unwrap(), + None, + ) + .unwrap(); + + let virtual_balance_query: GetBalanceResponse = virtual_balance_nibiru + .query(&euclid::msgs::virtual_balance::QueryMsg::GetBalance { + balance_key: BalanceKey { + cross_chain_user: CrossChainUser { + address: sender.clone(), + chain_uid: ChainUid::create("nibiru".to_string()).unwrap(), + }, + token_id: eucl_token.token.to_string(), + }, + }) + .unwrap(); + assert_eq!( + virtual_balance_query, + GetBalanceResponse { + amount: Uint128::from(50u128), + } + ); +} + +#[test] +fn test_add_liquidity() { + let sender = Addr::unchecked("sender_for_all_chains").into_string(); + let interchain = MockInterchainEnv::new(vec![("osmosis", &sender), ("nibiru", &sender)]); + let _factory_chain = interchain.get_chain("osmosis").unwrap(); + let router_chain = interchain.get_chain("nibiru").unwrap(); + + let router = crate::helpers::chains::setup_router(&router_chain); + let factory = crate::helpers::chains::setup_factory(&interchain, "osmosis", "nibiru", &router); + + let pair_info = PairWithDenomAndAmount { + token_1: TokenWithDenomAndAmount { + token: Token::create("eucl".to_string()).unwrap(), + amount: Uint128::from(10_000u128), + token_type: euclid::token::TokenType::Native { + denom: "eucl".to_string(), + }, + }, + token_2: TokenWithDenomAndAmount { + token: Token::create("nibi".to_string()).unwrap(), + amount: Uint128::from(100_000u128), + token_type: euclid::token::TokenType::Native { + denom: "nibi".to_string(), + }, + }, + }; + register_token( + &interchain, + &factory, + pair_info.token_1.to_token_with_denom(), + ); + create_pool(&interchain, &factory, pair_info.clone(), BPS_1_PERCENT); + + add_liquidity(&interchain, &factory, pair_info, BPS_1_PERCENT); +} diff --git a/tests-integration/src/tests/router.rs b/tests-integration/src/tests/router.rs index bc054bce..c424133e 100644 --- a/tests-integration/src/tests/router.rs +++ b/tests-integration/src/tests/router.rs @@ -1,58 +1,208 @@ #![cfg(not(target_arch = "wasm32"))] - -use cosmwasm_std::{coin, Addr}; -use euclid::msgs::router::StateResponse; -use mock::{mock::mock_app, mock_builder::MockEuclidBuilder}; - -use router::mock::{mock_router, MockRouter}; -use virtual_balance::mock::mock_virtual_balance; -use vlp::mock::mock_vlp; +use cosmwasm_std::Addr; +use cw_orch::prelude::*; +use cw_orch_interchain::types::IbcPacketOutcome; +use escrow::EscrowContract; +use euclid::chain::Chain; +use euclid::chain::ChainUid; +use euclid::chain::IbcChain; +use euclid::msgs::factory::InstantiateMsg as FactoryInstantiateMsg; +use euclid::msgs::router::AllChainResponse; +use euclid::msgs::router::ChainResponse; +use euclid::msgs::router::InstantiateMsg as RouterInstantiateMsg; +use euclid::msgs::router::RegisterFactoryChainIbc; +use factory::FactoryContract; const _USER: &str = "user"; const _NATIVE_DENOM: &str = "native"; const _IBC_DENOM_1: &str = "ibc/denom1"; const _IBC_DENOM_2: &str = "ibc/denom2"; const _SUPPLY: u128 = 1_000_000; +use cw_orch_interchain::prelude::*; +use cw_orch_interchain::InterchainEnv; +use router::RouterContract; +use virtual_balance::VirtualBalanceContract; #[test] -fn test_proper_instantiation() { - let mut router = mock_app(None); - let andr = MockEuclidBuilder::new(&mut router, "admin") - .with_wallets(vec![ - ("owner", vec![coin(1000, "eucl")]), - ("recipient1", vec![]), - ("recipient2", vec![]), - ]) - .with_contracts(vec![ - ("router", mock_router()), - ("vlp", mock_vlp()), - ("virtual_balance", mock_virtual_balance()), - ]) - .build(&mut router); - let owner = andr.get_wallet("owner"); - let _recipient_1 = andr.get_wallet("recipient1"); - let _recipient_2 = andr.get_wallet("recipient2"); - - let router_code_id = 1; - let vlp_code_id = 2; - let virtual_balance_code_id = 3; - - let mock_router = MockRouter::instantiate( - &mut router, - router_code_id, - owner.clone(), - vlp_code_id, - virtual_balance_code_id, - ); +fn test_register_factory() { + // Here `juno-1` is the chain-id and `juno` is the address prefix for this chain + let sender = Addr::unchecked("sender_for_all_chains").into_string(); + + let interchain = MockInterchainEnv::new(vec![("juno", &sender), ("osmosis", &sender)]); + + let juno = interchain.get_chain("juno").unwrap(); + let osmosis = interchain.get_chain("osmosis").unwrap(); + + juno.set_balance(sender.clone(), vec![Coin::new(100000000000000, "juno")]) + .unwrap(); + + let factory_juno = FactoryContract::new(juno.clone()); + let escrow_juno = EscrowContract::new(juno); + let router_osmo = RouterContract::new(osmosis.clone()); + let virtual_balance_osmo = VirtualBalanceContract::new(osmosis); + //Upload contracts + escrow_juno.upload().unwrap(); + factory_juno.upload().unwrap(); + router_osmo.upload().unwrap(); + virtual_balance_osmo.upload().unwrap(); + + let virtual_balance_code_id = virtual_balance_osmo.code_id().unwrap(); + let router_contract = "contract0".to_string(); + + factory_juno + .instantiate( + &FactoryInstantiateMsg { + router_contract, + chain_uid: ChainUid::create("junouid".to_string()).unwrap(), + escrow_code_id: 1, + cw20_code_id: 2, + is_native: false, + }, + None, + None, + ) + .unwrap(); + // Upload vbalance contract + + router_osmo + .instantiate( + &RouterInstantiateMsg { + vlp_code_id: 3, + virtual_balance_code_id, + }, + None, + None, + ) + .unwrap(); + + // Set up channel from juno to osmosis + // let channel_receipt = interchain + // .create_contract_channel(&factory_juno, &router_osmo, "counter-1", None) + // .unwrap(); + + // Set up channel from osmosis to juno + let channel_receipt_osmosis = interchain + .create_contract_channel(&router_osmo, &factory_juno, "counter-1", None) + .unwrap(); + + // After channel creation is complete, we get the channel id, which is necessary for ICA remote execution + // let juno_channel = channel_receipt + // .interchain_channel + // .get_chain("juno") + // .unwrap() + // .channel + // .unwrap(); + + // Register factory should be the first execute msg from router + let osmosis_channel = channel_receipt_osmosis + .interchain_channel + .get_chain("osmosis") + .unwrap() + .channel + .unwrap(); - let state = MockRouter::query_state(&mock_router, &mut router); - let expected_state_response = StateResponse { - admin: owner.clone().into_string(), - vlp_code_id, - virtual_balance_address: Some(Addr::unchecked( - "eucl1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3ys8rp88c", - )), - locked: false, + // Add a hub channel to factory-juno + factory_juno + .execute( + &euclid::msgs::factory::ExecuteMsg::UpdateHubChannel { + new_channel: osmosis_channel.to_string(), + }, + None, + ) + .unwrap(); + + let juno_uid = ChainUid::create("junouid".to_string()).unwrap(); + let register_factory_request = router_osmo + .execute( + &euclid::msgs::router::ExecuteMsg::RegisterFactory { + chain_uid: juno_uid.clone(), + chain_info: euclid::msgs::router::RegisterFactoryChainType::Ibc( + RegisterFactoryChainIbc { + channel: osmosis_channel.to_string(), + timeout: None, + }, + ), + }, + None, + ) + .unwrap(); + + let packet_lifetime = interchain + .await_packets("osmosis", register_factory_request) + .unwrap(); + + // For testing a successful outcome of the first packet sent out in the tx, you can use: + if let IbcPacketOutcome::Success { .. } = &packet_lifetime.packets[0].outcome { + // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + } else { + panic!("packet timed out"); + // There was a decode error or the packet timed out + // Else the packet timed-out, you may have a relayer error or something is wrong in your application }; - assert_eq!(state, expected_state_response); + + let all_chains: AllChainResponse = router_osmo + .query(&euclid::msgs::router::QueryMsg::GetAllChains {}) + .unwrap(); + + assert_eq!( + all_chains.chains, + vec![ChainResponse { + chain: Chain { + factory_chain_id: "juno".to_string(), + factory: "contract0".to_string(), + chain_type: euclid::chain::ChainType::Ibc(IbcChain { + from_hub_channel: "channel-0".to_string(), + from_factory_channel: "channel-0".to_string(), + }) + }, + chain_uid: juno_uid + }] + ); + + // Successful register factory // + + // let token_with_denom = TokenWithDenom { + // token: Token::create("juno".to_string()).unwrap(), + // token_type: TokenType::Native { + // denom: "juno".to_string(), + // }, + // }; + + // // we should request to register escrow on factory-juno + // let tx_response = factory_juno + // .execute( + // &euclid::msgs::factory::ExecuteMsg::RequestRegisterEscrow { + // token: token_with_denom.clone(), + // timeout: None, + // }, + // None, + // ) + // .unwrap(); + + // // This broadcasts a transaction on the client + // // It sends an IBC packet to the host + // let amount = Uint128::from(100u128); + + // let packet_lifetime = interchain.wait_ibc("juno", tx_response).unwrap(); + + // // For testing a successful outcome of the first packet sent out in the tx, you can use: + // if let IbcPacketOutcome::Success { ack, .. } = &packet_lifetime.packets[0].outcome { + // // Packet has been successfully acknowledged and decoded, the transaction has gone through correctly + // } else { + // panic!("packet timed out"); + // // There was a decode error or the packet timed out + // // Else the packet timed-out, you may have a relayer error or something is wrong in your application + // }; + + // let tx_response = factory_juno + // .execute( + // &euclid::msgs::factory::ExecuteMsg::DepositToken { + // asset_in: token_with_denom, + // amount_in: amount, + // recipient: None, + // timeout: None, + // }, + // Some(&coins(100u128, "juno")), + // ) + // .unwrap(); } diff --git a/tests-integration/src/tests/virtual_balance.rs b/tests-integration/src/tests/virtual_balance.rs index 3fbd9f85..de088e28 100644 --- a/tests-integration/src/tests/virtual_balance.rs +++ b/tests-integration/src/tests/virtual_balance.rs @@ -43,7 +43,7 @@ fn test_proper_instantiation() { ); let token_id_response = - MockVirtualBalance::query_state(&mock_virtual_balance, &mut virtual_balance); + MockVirtualBalance::query_state(&mock_virtual_balance, &virtual_balance); let expected_token_id = GetStateResponse { state: State { router: mock_router.addr().clone().into_string(), diff --git a/tests-integration/src/tests/vlp.rs b/tests-integration/src/tests/vlp.rs index d867a723..dc2f8204 100644 --- a/tests-integration/src/tests/vlp.rs +++ b/tests-integration/src/tests/vlp.rs @@ -90,7 +90,7 @@ fn test_proper_instantiation() { "admin".to_string(), ); - let token_id_response = MockVlp::query_state(&mock_vlp, &mut vlp); + let token_id_response = MockVlp::query_state(&mock_vlp, &vlp); let expected_token_id = GetStateResponse { pair, router: mock_router.addr().clone().into_string(),